- Add option to deploy third party package CLI tools
- Fix bug in generation of tarball downloads - Added some documentation
This commit is contained in:
parent
da4e2d914a
commit
f9829e8b9e
5 changed files with 184 additions and 18 deletions
62
README.md
62
README.md
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
74
src/Composer2Nix/Composer.php
Normal file
74
src/Composer2Nix/Composer.php
Normal 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");
|
||||
}
|
||||
}
|
||||
?>
|
|
@ -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);
|
||||
|
|
|
@ -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 ..
|
||||
|
|
Loading…
Reference in a new issue