Initial commit
This commit is contained in:
commit
e66636acb9
7 changed files with 475 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
vendor/
|
19
LICENSE
Normal file
19
LICENSE
Normal file
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2017 Sander van der Burg <svanderburg@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
40
README.md
Normal file
40
README.md
Normal file
|
@ -0,0 +1,40 @@
|
|||
composer2nix
|
||||
============
|
||||
`composer2nix` is a tool that can be used to generate [Nix](http://nixos.org)
|
||||
expressions for PHP [composer](https://getcomposer.org) packages.
|
||||
|
||||
Nix integration makes it possible to use the Nix package manager (as opposed to
|
||||
composer) to deploy PHP packages including all their required dependencies.
|
||||
|
||||
In addition, generated Nix composer packages
|
||||
support convenient integration of PHP applications with NixOS services, such as
|
||||
NixOS' Apache HTTP service.
|
||||
|
||||
Usage
|
||||
=====
|
||||
You need a project providing both a `composer.json` and a `composer.lock`
|
||||
configuration file.
|
||||
|
||||
Running the following command generates Nix expressions from the composer
|
||||
configuration files:
|
||||
|
||||
$ composer2nix
|
||||
|
||||
The above command produces three expressions: `php-packages.nix` containing the
|
||||
dependencies, `composer-env.nix` the build infrastructure and `default.nix` that
|
||||
can be used to compose the package from its dependencies.
|
||||
|
||||
Running the following command-line instruction deploys the package with Nix
|
||||
including its dependencies:
|
||||
|
||||
$ nix-build
|
||||
|
||||
Limitations
|
||||
===========
|
||||
Currently, the state of this tool is that it is just a proof on concept
|
||||
implementation. As a result, it is lacking many features and probably buggy.
|
||||
Most importantly, only the `zip` and `git` dependencies are supported.
|
||||
|
||||
License
|
||||
=======
|
||||
The contents of this package is available under the [MIT license](http://opensource.org/licenses/MIT)
|
132
bin/composer2nix.php
Executable file
132
bin/composer2nix.php
Executable file
|
@ -0,0 +1,132 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
require_once(dirname(__FILE__)."/../vendor/autoload.php");
|
||||
|
||||
use Composer2Nix\Generator;
|
||||
|
||||
function displayHelp($command)
|
||||
{
|
||||
print("Usage: ".$command." [OPTION]\n\n");
|
||||
echo <<<EOT
|
||||
This executable can be used to generate Nix expressions from a composer.lock
|
||||
(and a composer.json) file so that a package and all its dependencies can be
|
||||
deployed by the Nix package manager.
|
||||
|
||||
Options:
|
||||
--config-file=FILE Path to the composer.json file (defaults to:
|
||||
composer.json)
|
||||
--lock-file=FILE Path to the composer.lock file (defaults to:
|
||||
composer.lock)
|
||||
--output=FILE Path to the Nix expression containing the generated
|
||||
packages (defaults to: php-packages.nix)
|
||||
--composition=FILE Path to the Nix expression that composes the package
|
||||
(defaults to: default.nix)
|
||||
--composer-env=FILE Path to the Nix expression deploying the composer
|
||||
packages (defaults to: composer-env.nix)
|
||||
--prefer-source Forces installation from package sources when possible
|
||||
--prefer-dist Forces installation from package dist
|
||||
--name Name of the generated package (defaults to the name
|
||||
provided in the composer.json file)
|
||||
--no-copy-composer-env
|
||||
Do not create a copy of the Nix expression that builds
|
||||
composer packages
|
||||
-h, --help Shows the usage of this command
|
||||
-v, --version Shows the version of this command
|
||||
|
||||
EOT;
|
||||
}
|
||||
|
||||
function displayVersion($command)
|
||||
{
|
||||
print($command." (composer2nix 0.0.1)\n\nCopyright (C) 2017 Sander van der Burg\n");
|
||||
}
|
||||
|
||||
/* Parse command line options */
|
||||
|
||||
$options = getopt("hv", array(
|
||||
"config-file:",
|
||||
"lock-file:",
|
||||
"output:",
|
||||
"composition:",
|
||||
"composer-env:",
|
||||
"prefer-source",
|
||||
"prefer-dist",
|
||||
"name:",
|
||||
"no-copy-composer-env",
|
||||
"help",
|
||||
"version"
|
||||
));
|
||||
|
||||
if($options === false)
|
||||
{
|
||||
fwrite(STDERR, "Cannot parse the command-line options!\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Parse the options themselves */
|
||||
|
||||
if(array_key_exists("h", $options) || array_key_exists("help", $options))
|
||||
{
|
||||
displayHelp($argv[0]);
|
||||
exit();
|
||||
}
|
||||
|
||||
if(array_key_exists("v", $options) || array_key_exists("version", $options))
|
||||
{
|
||||
displayVersion($argv[0]);
|
||||
exit();
|
||||
}
|
||||
|
||||
if(array_key_exists("config-file", $options))
|
||||
$configFile = $options["config-file"];
|
||||
else
|
||||
$configFile = "composer.json";
|
||||
|
||||
if(array_key_exists("lock-file", $options))
|
||||
$lockFile = $options["lock-file"];
|
||||
else
|
||||
$lockFile = "composer.lock";
|
||||
|
||||
|
||||
if(array_key_exists("output", $options))
|
||||
$outputFile = $options["output"];
|
||||
else
|
||||
$outputFile = "php-packages.nix";
|
||||
|
||||
if(array_key_exists("composition", $options))
|
||||
$compositionFile = $options["composition"];
|
||||
else
|
||||
$compositionFile = "default.nix";
|
||||
|
||||
if(array_key_exists("composer-env", $options))
|
||||
$composerEnvFile = $options["composer-env"];
|
||||
else
|
||||
$composerEnvFile = "composer-env.nix";
|
||||
|
||||
$noCopyComposerEnv = array_key_exists("no-copy-composer-env", $options);
|
||||
|
||||
$installType = "dist"; // TODO: consult composer.json's preferred-install property. defaults to: auto
|
||||
|
||||
if(array_key_exists("prefer-source", $options))
|
||||
$installType = "source";
|
||||
|
||||
if(array_key_exists("prefer-dist", $options))
|
||||
$installType = "dist";
|
||||
|
||||
if(array_key_exists("name", $options))
|
||||
$name = $options["name"];
|
||||
else
|
||||
$name = null;
|
||||
|
||||
/* Execute the generator */
|
||||
|
||||
try
|
||||
{
|
||||
Generator::generateNixExpressions($name, $installType, $configFile, $lockFile, $outputFile, $compositionFile, $composerEnvFile, $noCopyComposerEnv);
|
||||
}
|
||||
catch(Exception $ex)
|
||||
{
|
||||
fwrite(STDERR, $ex->getMessage()."\n");
|
||||
exit(1);
|
||||
}
|
||||
?>
|
19
composer.json
Normal file
19
composer.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "svanderburg/composer2nix",
|
||||
"description": "Generate Nix expressions to build PHP composer packages",
|
||||
"type": "library",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Sander van der Burg",
|
||||
"email": "svanderburg@gmail.com",
|
||||
"homepage": "http://sandervanderburg.nl"
|
||||
}
|
||||
],
|
||||
|
||||
"autoload": {
|
||||
"psr-4": { "Composer2Nix\\": "src/Composer2Nix" }
|
||||
},
|
||||
|
||||
"bin": [ "bin/composer2nix" ]
|
||||
}
|
140
src/Composer2Nix/Generator.php
Normal file
140
src/Composer2Nix/Generator.php
Normal file
|
@ -0,0 +1,140 @@
|
|||
<?php
|
||||
namespace Composer2Nix;
|
||||
use Exception;
|
||||
|
||||
class Generator
|
||||
{
|
||||
function generateNixExpressions($name, $installType, $configFile, $lockFile, $outputFile, $compositionFile, $composerEnvFile, $noCopyComposerEnv)
|
||||
{
|
||||
/* Open the composer.json file and decode it */
|
||||
$composerJSONStr = file_get_contents($configFile);
|
||||
|
||||
if($composerJSONStr === false)
|
||||
throw new Exception("Cannot open contents of: ".$configFile);
|
||||
|
||||
$config = json_decode($composerJSONStr, true);
|
||||
|
||||
/* If no package name has been provided, attempt to use the name in the composer config file */
|
||||
if($name === null)
|
||||
{
|
||||
if(array_key_exists("name", $config))
|
||||
$name = $config["name"];
|
||||
else
|
||||
{
|
||||
throw new Exception("Cannot determine a package name! Either add a name\n".
|
||||
"property to the composer.json file or provide a --name parameter!");
|
||||
}
|
||||
}
|
||||
|
||||
$name = strtr($name, "/", "-"); // replace / by - since / is not allowed in Nix package names
|
||||
|
||||
/* Open the lock file and decode it */
|
||||
|
||||
if(file_exists($lockFile))
|
||||
{
|
||||
$composerLockStr = file_get_contents($lockFile);
|
||||
|
||||
if($composerLockStr === false)
|
||||
throw new Exception("Cannot open contents of: ".$lockFile);
|
||||
|
||||
$lockConfig = json_decode($composerLockStr, true);
|
||||
}
|
||||
else
|
||||
$lockConfig = null;
|
||||
|
||||
/* Generate packages expression */
|
||||
|
||||
$handle = fopen($outputFile, "w");
|
||||
|
||||
if($handle === false)
|
||||
throw new Exception("Cannot write to: ".$outputFile);
|
||||
|
||||
if($lockConfig === null)
|
||||
$packages = array();
|
||||
else
|
||||
$packages = $lockConfig["packages"];
|
||||
|
||||
fwrite($handle, "{composerEnv, fetchgit ? null}:\n\n");
|
||||
fwrite($handle, "let\n");
|
||||
fwrite($handle, " dependencies = {\n");
|
||||
|
||||
foreach($packages as $package)
|
||||
{
|
||||
$sourceObj = $package[$installType];
|
||||
|
||||
switch($sourceObj["type"])
|
||||
{
|
||||
case "zip":
|
||||
$hash = shell_exec('nix-prefetch-url "'.$sourceObj['url'].'"');
|
||||
fwrite($handle, ' "'.$package["name"].'" = composerEnv.buildZipPackage {'."\n");
|
||||
fwrite($handle, ' name = "'.strtr($package["name"], "/", "-").'-'.$sourceObj["reference"].'";'."\n");
|
||||
fwrite($handle, ' url = "'.$sourceObj["url"].'";'."\n");
|
||||
fwrite($handle, ' sha256 = "'.substr($hash, 0, -1).'";'."\n");
|
||||
break;
|
||||
case "git":
|
||||
$outputStr = shell_exec('nix-prefetch-git "'.$sourceObj['url'].'" '.$sourceObj["reference"]);
|
||||
|
||||
$output = json_decode($outputStr, true);
|
||||
$hash = $output["sha256"];
|
||||
|
||||
fwrite($handle, ' "'.$package["name"].'" = fetchgit {'."\n");
|
||||
fwrite($handle, ' name = "'.strtr($package["name"], "/", "-").'-'.$sourceObj["reference"].'";'."\n");
|
||||
fwrite($handle, ' url = "'.$sourceObj["url"].'";'."\n");
|
||||
fwrite($handle, ' rev = "'.$sourceObj["reference"].'";'."\n");
|
||||
fwrite($handle, ' sha256 = "'.$hash.'";'."\n");
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Cannot convert dependency of type: ".$sourceObj["type"]);
|
||||
}
|
||||
|
||||
fwrite($handle, " };\n");
|
||||
}
|
||||
|
||||
fwrite($handle, " };\n");
|
||||
fwrite($handle, "in\n");
|
||||
fwrite($handle, "composerEnv.buildPackage {\n");
|
||||
fwrite($handle, ' name = "'.$name.'";'."\n");
|
||||
fwrite($handle, " src = ./.;\n");
|
||||
fwrite($handle, " inherit dependencies;\n");
|
||||
fwrite($handle, "}\n");
|
||||
|
||||
fclose($handle);
|
||||
|
||||
/* Generate composition expression */
|
||||
|
||||
$handle = fopen($compositionFile, "w");
|
||||
|
||||
if($handle === false)
|
||||
throw new Exception("Cannot write to: ".$compositionFile);
|
||||
|
||||
function composeNixFilePath($path)
|
||||
{
|
||||
if((strlen($path) > 0 && substr($path, 0, 1) === "/") || (strlen($path) > 1 && substr($path, 0, 2) === "./"))
|
||||
return $path;
|
||||
else
|
||||
return "./".$path;
|
||||
}
|
||||
|
||||
fwrite($handle, "{ pkgs ? import <nixpkgs> { inherit system; }\n");
|
||||
fwrite($handle, ", system ? builtins.currentSystem\n");
|
||||
fwrite($handle, "}:\n\n");
|
||||
|
||||
fwrite($handle, "let\n");
|
||||
fwrite($handle, " composerEnv = import ".composeNixFilePath($composerEnvFile)." {\n");
|
||||
fwrite($handle, " inherit (pkgs) stdenv writeTextFile fetchurl php unzip;\n");
|
||||
fwrite($handle, " };\n");
|
||||
fwrite($handle, "in\n");
|
||||
fwrite($handle, "import ".composeNixFilePath($outputFile)." {\n");
|
||||
fwrite($handle, " inherit composerEnv;\n");
|
||||
fwrite($handle, " inherit (pkgs) fetchgit;\n");
|
||||
fwrite($handle, "}\n");
|
||||
|
||||
fclose($handle);
|
||||
|
||||
/* Copy composer-env.nix */
|
||||
|
||||
if(!$noCopyComposerEnv && !copy(dirname(__FILE__)."/composer-env.nix", $composerEnvFile))
|
||||
throw new Exception("Cannot copy node-env.nix!");
|
||||
}
|
||||
}
|
||||
?>
|
124
src/Composer2Nix/composer-env.nix
Normal file
124
src/Composer2Nix/composer-env.nix
Normal file
|
@ -0,0 +1,124 @@
|
|||
# This file originates from composer2nix
|
||||
|
||||
{ stdenv, writeTextFile, fetchurl, php, unzip }:
|
||||
|
||||
rec {
|
||||
composer = stdenv.mkDerivation {
|
||||
name = "composer-1.4.2";
|
||||
src = fetchurl {
|
||||
url = https://github.com/composer/composer/releases/download/1.4.2/composer.phar;
|
||||
sha256 = "1x467ngxb976ba2r9kqba7jpvm95a0db8nwaa2z14zs7xv1la6bb";
|
||||
};
|
||||
buildInputs = [ php ];
|
||||
|
||||
# We must wrap the composer.phar because of the impure shebang.
|
||||
# We cannot use patchShebangs because the executable verifies its own integrity and will detect that somebody has tampered with it.
|
||||
|
||||
buildCommand = ''
|
||||
# Copy phar file
|
||||
mkdir -p $out/share/php
|
||||
cp $src $out/share/php/composer.phar
|
||||
chmod 755 $out/share/php/composer.phar
|
||||
|
||||
# Create wrapper executable
|
||||
mkdir -p $out/bin
|
||||
cat > $out/bin/composer <<EOF
|
||||
#! ${stdenv.shell} -e
|
||||
exec ${php}/bin/php $out/share/php/composer.phar "\$@"
|
||||
EOF
|
||||
chmod +x $out/bin/composer
|
||||
'';
|
||||
meta = {
|
||||
description = "Dependency Manager for PHP";
|
||||
#license = stdenv.licenses.mit;
|
||||
maintainers = [ stdenv.lib.maintainers.sander ];
|
||||
platforms = stdenv.lib.platforms.unix;
|
||||
};
|
||||
};
|
||||
|
||||
buildZipPackage = { name, url, sha256 }:
|
||||
stdenv.mkDerivation {
|
||||
inherit name;
|
||||
src = fetchurl {
|
||||
inherit url sha256;
|
||||
};
|
||||
buildInputs = [ unzip ];
|
||||
buildCommand = ''
|
||||
unzip $src
|
||||
baseDir=$(find . -type d -mindepth 1 -maxdepth 1)
|
||||
cd $baseDir
|
||||
mkdir -p $out
|
||||
mv * $out
|
||||
'';
|
||||
};
|
||||
|
||||
buildPackage = { name, src, dependencies ? [], removeComposerArtifacts ? false }:
|
||||
let
|
||||
reconstructInstalled = writeTextFile {
|
||||
name = "reconstructinstalled.php";
|
||||
executable = true;
|
||||
text = ''
|
||||
#! ${php}/bin/php
|
||||
<?php
|
||||
$composerLockStr = file_get_contents($argv[1]);
|
||||
|
||||
if($composerLockStr === false)
|
||||
{
|
||||
print("Cannot open composer.lock contents");
|
||||
exit(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
$config = json_decode($composerLockStr);
|
||||
$packagesStr = json_encode($config->packages, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
print($packagesStr);
|
||||
}
|
||||
?>
|
||||
'';
|
||||
};
|
||||
in
|
||||
stdenv.mkDerivation {
|
||||
inherit name src;
|
||||
buildInputs = [ php composer ];
|
||||
buildCommand = ''
|
||||
cp -av $src $out
|
||||
chmod -R u+w $out
|
||||
cd $out
|
||||
|
||||
# Remove unwanted files
|
||||
rm -f *.nix
|
||||
|
||||
export HOME=$TMPDIR
|
||||
|
||||
# Reconstruct the installed.json file from the lock file
|
||||
mkdir -p vendor/composer
|
||||
${reconstructInstalled} composer.lock > vendor/composer/installed.json
|
||||
|
||||
# Symlink the provided dependencies
|
||||
cd vendor
|
||||
${stdenv.lib.concatMapStrings (dependencyName:
|
||||
let
|
||||
dependency = dependencies.${dependencyName};
|
||||
in
|
||||
''
|
||||
vendorDir="$(dirname ${dependencyName})"
|
||||
mkdir -p "$vendorDir"
|
||||
ln -s "${dependency}" "$vendorDir/$(basename "${dependencyName}")"
|
||||
'') (builtins.attrNames dependencies)}
|
||||
cd ..
|
||||
|
||||
# Reconstruct autoload scripts
|
||||
# We use the optimize feature because Nix packages cannot change after they have been built
|
||||
# Using the dynamic loader for a Nix package is useless since there is nothing to dynamically reload.
|
||||
composer dump-autoload --optimize
|
||||
|
||||
# Run the install step as a validation to confirm that everything works out as expected
|
||||
composer install --optimize-autoloader
|
||||
|
||||
${stdenv.lib.optionalString (removeComposerArtifacts) ''
|
||||
# Remove composer stuff
|
||||
rm composer.json composer.lock
|
||||
''}
|
||||
'';
|
||||
};
|
||||
}
|
Loading…
Reference in a new issue