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