- Add option to deploy third party package CLI tools

- Fix bug in generation of tarball downloads
- Added some documentation
This commit is contained in:
Sander van der Burg 2017-09-18 22:47:28 +02:00
parent da4e2d914a
commit f9829e8b9e
5 changed files with 184 additions and 18 deletions

View file

@ -10,10 +10,19 @@ In addition, generated Nix composer packages
support convenient integration of PHP applications with NixOS services, such as
NixOS' Apache HTTP service.
Prerequisites
=============
This package requires the following packages to be installed:
* [Nix package manager](http://nixos.org/nix)
* The Nix prefetch scripts, e.g: `nix-env -f '<nixpkgs>' -iA nix-prefetch-scripts`
Consult the Nix documentation for the installation instructions.
Usage
=====
You need a project providing both a `composer.json` and a `composer.lock`
configuration file.
You need a project providing a `composer.json` and (if applicable) a
`composer.lock` configuration file.
Running the following command generates Nix expressions from the composer
configuration files:
@ -117,9 +126,7 @@ in
We can deploy the above NixOS configuration as follows:
```bash
$ nixos-rebuild switch
```
$ nixos-rebuild switch
If the above command succeeds, we have a running system with the Apache
webserver serving our web application.
@ -138,6 +145,51 @@ We can install the `composer2nix` executable in our Nix profile by running:
$ nix-env -f default.nix -i
Deploying third-party end user packages
---------------------------------------
Aside from deploying development projects, we may also want to deploy third
party end-user packages, typically command-line tools.
We can use `composer2nix` to automatically generate expressions from a third
party package that comes from Packagist, such as `phpunit`:
$ composer2nix -p phpunit/phpunit
After generating the expressions, we can deploy `phpunit` in our Nix profile,
by running:
$ nix-env -f default.nix -iA phpunit-phpunit
And after installing the package with Nix, we should be able to run:
$ phpunit --version
By default, `composer2nix` attempts to download the latest version of a package.
We can also add a parameter that specifies the version we want to use:
$ composer2nix -p phpunit/phpunit --package-version 6.2.0
The above command-line instruction deploys `phpunit` version `6.2.0`.
The `--package-version` parameter supports any version specifier supported by
`composer`, including version ranges.
Advanced features
=================
`composer2nix` supports a number of less commonly used advanced features.
Symlinking dependencies
-----------------------
By default, `composer2nix` makes copies of all packages that end up in the
`vendor/` folder. This is the default option, because some packages load the
`autoload.php` relative from its resolved location, such as `phpunit` and may
not work properly if a dependency is a symlink.
It is also possible to symlink all dependencies as opposed to copying them which
makes deployments faster and more space efficient:
$ composer2nix --symlink-dependencies
Limitations
===========
Currently, the state of this tool is that it is just a proof on concept

View file

@ -2,6 +2,7 @@
<?php
require_once(dirname(__FILE__)."/../vendor/autoload.php");
use Composer2Nix\Composer;
use Composer2Nix\Generator;
function displayHelp($command)
@ -17,6 +18,9 @@ Options:
composer.json)
--lock-file=FILE Path to the composer.lock file (defaults to:
composer.lock)
-p, --package Package to deploy as a command-line utility
--package-version Preferred version of the package to deploy (defaults
to the latest version)
--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
@ -33,6 +37,10 @@ Options:
--no-copy-composer-env
Do not create a copy of the Nix expression that builds
composer packages
--symlink-deps Symlink the dependencies as opposed to copying them.
This is more space efficient, but not all packages may
be able to load their dependencies if they depend on
symlink resolving (defaults to: false).
-h, --help Shows the usage of this command
-v, --version Shows the version of this command
@ -46,9 +54,11 @@ function displayVersion($command)
/* Parse command line options */
$options = getopt("hv", array(
$options = getopt("p:hv", array(
"config-file:",
"lock-file:",
"package:",
"package-version:",
"output:",
"composition:",
"composer-env:",
@ -58,6 +68,7 @@ $options = getopt("hv", array(
"name:",
"executable",
"no-copy-composer-env",
"symlink-deps",
"help",
"version"
));
@ -108,6 +119,18 @@ if(array_key_exists("composer-env", $options))
else
$composerEnvFile = "composer-env.nix";
if(array_key_exists("p", $options))
$package = $options["p"];
else if(array_key_exists("package", $options))
$package = $options["package"];
else
$package = null;
if(array_key_exists("package-version", $options))
$versionSpec = $options["package-version"];
else
$versionSpec = null;
$noCopyComposerEnv = array_key_exists("no-copy-composer-env", $options);
$preferredInstall = "dist"; // TODO: consult composer.json's preferred-install property. defaults to: auto
@ -125,13 +148,21 @@ if(array_key_exists("name", $options))
else
$name = null;
$symlinkDependencies = array_key_exists("symlink-deps", $options);
$executable = array_key_exists("executable", $options);
/* Execute the generator */
try
{
Generator::generateNixExpressions($name, $executable, $preferredInstall, $noDev, $configFile, $lockFile, $outputFile, $compositionFile, $composerEnvFile, $noCopyComposerEnv);
if($package === null)
Generator::generateNixExpressions($name, $executable, $preferredInstall, $noDev, $configFile, $lockFile, $outputFile, $compositionFile, $composerEnvFile, $noCopyComposerEnv, $symlinkDependencies);
else
{
Composer::composePackageFromDependency($package, $versionSpec, $preferredInstall, $noDev);
Generator::generateNixExpressions($package, true, $preferredInstall, $noDev, "composer.json", "composer.lock", $outputFile, $compositionFile, $composerEnvFile, $noCopyComposerEnv, $symlinkDependencies);
}
}
catch(Exception $ex)
{

View file

@ -0,0 +1,74 @@
<?php
namespace Composer2Nix;
use Exception;
class Composer
{
private static function rrmdir($dir)
{
if(is_dir($dir))
{
$objects = scandir($dir);
foreach($objects as $object)
{
if($object != "." && $object != "..")
{
if(is_dir($dir."/".$object))
Composer::rrmdir($dir."/".$object);
else
unlink($dir."/".$object);
}
}
rmdir($dir);
}
}
public static function composePackageFromDependency($dependencyName, $versionSpec, $preferredInstall, $noDev)
{
/* Generate a composer.json file with only the requested dependency */
$handle = fopen("composer.json", "w");
if($handle === false)
throw new Exception("Cannot write to: composer.json");
fwrite($handle, "{\n");
fwrite($handle, ' "require": {'."\n");
if($versionSpec === null)
$versionSpec = "*";
fwrite($handle, ' "'.$dependencyName.'": "'.$versionSpec.'"'."\n");
fwrite($handle, " }\n");
fwrite($handle, "}\n");
fclose($handle);
/* Run composer to get a lock file showing the dependencies */
if($noDev)
$params = "--no-dev";
else
$params = "";
if($preferredInstall == "source")
$params .= " --prefer-source";
else if($preferredInstall == "dist")
$params .= " --prefer-dist";
$composerPath = shell_exec("nix-build --no-out-link -E 'let pkgs = import <nixpkgs> {}; composerEnv = import ".__DIR__."/composer-env.nix { inherit (pkgs) stdenv writeTextFile fetchurl php unzip; }; in composerEnv.composer'");
if($composerPath === false)
throw new Exception("Cannot deploy the composer Nix package!");
$composerExecutable = substr($composerPath, 0, -1)."/bin/composer";
$result = shell_exec($composerExecutable." ".$params." install");
if($result === false)
throw new Exception("Failed running composer!\n");
print($result."\n");
/* Remove vendor folder */
Composer::rrmdir("vendor");
}
}
?>

View file

@ -46,7 +46,7 @@ class Generator
return "./".$target;
}
private static function generatePackagesExpression($outputFile, $name, $preferredInstall, array $packages, $executable)
private static function generatePackagesExpression($outputFile, $name, $preferredInstall, array $packages, $executable, $symlinkDependencies)
{
$handle = fopen($outputFile, "w");
@ -89,10 +89,10 @@ class Generator
else
$src = new NixFile($sourceObj['url']);
$dependency["src"] = new NixFunInvocation(new NixExpression("composerEnv.buildZipPackage", array(
$dependency["src"] = new NixFunInvocation(new NixExpression("composerEnv.buildZipPackage"), array(
"name" => strtr($package["name"], "/", "-").$reference,
"src" => $src
)));
));
break;
case "git":
@ -148,7 +148,8 @@ class Generator
"name" => $name,
"src" => new NixFile("./."),
"executable" => $executable,
"dependencies" => new NixInherit()
"dependencies" => new NixInherit(),
"symlinkDependencies" => $symlinkDependencies
))));
$exprStr = NixGenerator::phpToNix($expr, true);
@ -189,7 +190,7 @@ class Generator
fclose($handle);
}
public static function generateNixExpressions($name, $executable, $preferredInstall, $noDev, $configFile, $lockFile, $outputFile, $compositionFile, $composerEnvFile, $noCopyComposerEnv)
public static function generateNixExpressions($name, $executable, $preferredInstall, $noDev, $configFile, $lockFile, $outputFile, $compositionFile, $composerEnvFile, $noCopyComposerEnv, $symlinkDependencies)
{
/* Open the composer.json file and decode it */
$composerJSONStr = file_get_contents($configFile);
@ -239,7 +240,7 @@ class Generator
$packages = array();
/* Generate packages expression */
Generator::generatePackagesExpression($outputFile, $name, $preferredInstall, $packages, $executable);
Generator::generatePackagesExpression($outputFile, $name, $preferredInstall, $packages, $executable, $symlinkDependencies);
/* Generate composition expression */
Generator::generateCompositionExpression($compositionFile, $outputFile, $composerEnvFile);

View file

@ -10,10 +10,10 @@ rec {
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
@ -49,7 +49,7 @@ rec {
'';
};
buildPackage = { name, src, dependencies ? [], executable ? false, removeComposerArtifacts ? false }:
buildPackage = { name, src, dependencies ? [], symlinkDependencies ? false, executable ? false, removeComposerArtifacts ? false }:
let
reconstructInstalled = writeTextFile {
name = "reconstructinstalled.php";
@ -150,11 +150,19 @@ rec {
${if dependency.targetDir == "" then ''
vendorDir="$(dirname ${dependencyName})"
mkdir -p "$vendorDir"
ln -s "${dependency.src}" "$vendorDir/$(basename "${dependencyName}")"
${if symlinkDependencies then
''ln -s "${dependency.src}" "$vendorDir/$(basename "${dependencyName}")"''
else
''cp -av "${dependency.src}" "$vendorDir/$(basename "${dependencyName}")"''
}
'' else ''
namespaceDir="${dependencyName}/$(dirname "${dependency.targetDir}")"
mkdir -p "$namespaceDir"
ln -s "${dependency.src}" "$namespaceDir/$(basename "${dependency.targetDir}")"
${if symlinkDependencies then
''ln -s "${dependency.src}" "$namespaceDir/$(basename "${dependency.targetDir}")"''
else
''cp -av "${dependency.src}" "$namespaceDir/$(basename "${dependency.targetDir}")"''
}
''}
'') (builtins.attrNames dependencies)}
cd ..