- Split generator function
- Add hg dependency support - Add example case
This commit is contained in:
parent
e66636acb9
commit
c46bf0a777
5 changed files with 244 additions and 101 deletions
93
README.md
93
README.md
|
@ -29,11 +29,102 @@ including its dependencies:
|
||||||
|
|
||||||
$ nix-build
|
$ nix-build
|
||||||
|
|
||||||
|
An example use case scenario
|
||||||
|
============================
|
||||||
|
We can use `composer2nix` to automate the deployment of a web application as
|
||||||
|
part of a NixOS configuration.
|
||||||
|
|
||||||
|
For example, we can create the following trivial PHP web application
|
||||||
|
(`index.php`) that uses the [dompdf](http://dompdf.github.io/) library to
|
||||||
|
generate a PDF file from an HTML page:
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
require 'vendor/autoload.php';
|
||||||
|
|
||||||
|
use Dompdf\Dompdf;
|
||||||
|
|
||||||
|
$dompdf = new Dompdf();
|
||||||
|
$dompdf->loadHtml('hello world');
|
||||||
|
|
||||||
|
$dompdf->setPaper('A4', 'landscape');
|
||||||
|
$dompdf->render();
|
||||||
|
|
||||||
|
$dompdf->stream();
|
||||||
|
?>
|
||||||
|
```
|
||||||
|
|
||||||
|
We can write the following `composer.json` configuration file to configure the
|
||||||
|
`dompdf` dependency:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "exampleapp/exampleapp",
|
||||||
|
|
||||||
|
"require": {
|
||||||
|
"dompdf/dompdf": "^0.8.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
With the following commmand we can let `composer` deploy the dependencies (and
|
||||||
|
pinpoint the used versions in a `composer.lock` file):
|
||||||
|
|
||||||
|
$ composer install
|
||||||
|
|
||||||
|
Instead, we can also use `composer2nix`:
|
||||||
|
|
||||||
|
$ composer2nix
|
||||||
|
|
||||||
|
The above command generates Nix expressions that can be used to deploy the web
|
||||||
|
application and its dependencies.
|
||||||
|
|
||||||
|
We can use Nix to build a bundle of our web application including its
|
||||||
|
dependencies:
|
||||||
|
|
||||||
|
$ nix-build
|
||||||
|
$ ls result/
|
||||||
|
index.php vendor/
|
||||||
|
|
||||||
|
(As may be observed, the `vendor/` folder contains all dependency artifacts).
|
||||||
|
|
||||||
|
We can attach the generated package to a document root of the Apache server in
|
||||||
|
a NixOS configuration:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{pkgs, config, ...}:
|
||||||
|
|
||||||
|
let
|
||||||
|
myexampleapp = import /home/sander/myexampleapp {
|
||||||
|
inherit pkgs;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
services.httpd = {
|
||||||
|
enable = true;
|
||||||
|
adminAddr = "admin@localhost";
|
||||||
|
extraModules = [
|
||||||
|
{ name = "php7"; path = "${pkgs.php}/modules/libphp7.so"; }
|
||||||
|
];
|
||||||
|
documentRoot = myexampleapp;
|
||||||
|
};
|
||||||
|
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
We can deploy the above NixOS configuration as follows:
|
||||||
|
|
||||||
|
$ nixos-rebuild switch
|
||||||
|
|
||||||
|
If the above command succeeds, we have a running system with the Apache
|
||||||
|
webserver serving our web application.
|
||||||
|
|
||||||
Limitations
|
Limitations
|
||||||
===========
|
===========
|
||||||
Currently, the state of this tool is that it is just a proof on concept
|
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.
|
implementation. As a result, it is lacking many features and probably buggy.
|
||||||
Most importantly, only the `zip` and `git` dependencies are supported.
|
Most importantly, only the `zip`, `git`, and `hg` dependencies are supported.
|
||||||
|
|
||||||
License
|
License
|
||||||
=======
|
=======
|
||||||
|
|
|
@ -25,6 +25,7 @@ Options:
|
||||||
packages (defaults to: composer-env.nix)
|
packages (defaults to: composer-env.nix)
|
||||||
--prefer-source Forces installation from package sources when possible
|
--prefer-source Forces installation from package sources when possible
|
||||||
--prefer-dist Forces installation from package dist
|
--prefer-dist Forces installation from package dist
|
||||||
|
--no-dev Do not install the development packages
|
||||||
--name Name of the generated package (defaults to the name
|
--name Name of the generated package (defaults to the name
|
||||||
provided in the composer.json file)
|
provided in the composer.json file)
|
||||||
--no-copy-composer-env
|
--no-copy-composer-env
|
||||||
|
@ -51,6 +52,7 @@ $options = getopt("hv", array(
|
||||||
"composer-env:",
|
"composer-env:",
|
||||||
"prefer-source",
|
"prefer-source",
|
||||||
"prefer-dist",
|
"prefer-dist",
|
||||||
|
"no-dev",
|
||||||
"name:",
|
"name:",
|
||||||
"no-copy-composer-env",
|
"no-copy-composer-env",
|
||||||
"help",
|
"help",
|
||||||
|
@ -105,13 +107,18 @@ else
|
||||||
|
|
||||||
$noCopyComposerEnv = array_key_exists("no-copy-composer-env", $options);
|
$noCopyComposerEnv = array_key_exists("no-copy-composer-env", $options);
|
||||||
|
|
||||||
$installType = "dist"; // TODO: consult composer.json's preferred-install property. defaults to: auto
|
$preferredInstall = "dist"; // TODO: consult composer.json's preferred-install property. defaults to: auto
|
||||||
|
|
||||||
if(array_key_exists("prefer-source", $options))
|
if(array_key_exists("prefer-source", $options))
|
||||||
$installType = "source";
|
$preferredInstall = "source";
|
||||||
|
|
||||||
if(array_key_exists("prefer-dist", $options))
|
if(array_key_exists("prefer-dist", $options))
|
||||||
$installType = "dist";
|
$preferredInstall = "dist";
|
||||||
|
|
||||||
|
if(array_key_exists("no-dev", $options))
|
||||||
|
$noDev = true;
|
||||||
|
else
|
||||||
|
$noDev = false;
|
||||||
|
|
||||||
if(array_key_exists("name", $options))
|
if(array_key_exists("name", $options))
|
||||||
$name = $options["name"];
|
$name = $options["name"];
|
||||||
|
@ -122,7 +129,7 @@ else
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Generator::generateNixExpressions($name, $installType, $configFile, $lockFile, $outputFile, $compositionFile, $composerEnvFile, $noCopyComposerEnv);
|
Generator::generateNixExpressions($name, $preferredInstall, $noDev, $configFile, $lockFile, $outputFile, $compositionFile, $composerEnvFile, $noCopyComposerEnv);
|
||||||
}
|
}
|
||||||
catch(Exception $ex)
|
catch(Exception $ex)
|
||||||
{
|
{
|
|
@ -4,15 +4,15 @@
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
"name": "Sander van der Burg",
|
"name": "Sander van der Burg",
|
||||||
"email": "svanderburg@gmail.com",
|
"email": "svanderburg@gmail.com",
|
||||||
"homepage": "http://sandervanderburg.nl"
|
"homepage": "http://sandervanderburg.nl"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": { "Composer2Nix\\": "src/Composer2Nix" }
|
"psr-4": { "Composer2Nix\\": "src/Composer2Nix" }
|
||||||
},
|
},
|
||||||
|
|
||||||
"bin": [ "bin/composer2nix" ]
|
"bin": [ "bin/composer2nix" ]
|
||||||
|
|
|
@ -4,7 +4,125 @@ use Exception;
|
||||||
|
|
||||||
class Generator
|
class Generator
|
||||||
{
|
{
|
||||||
function generateNixExpressions($name, $installType, $configFile, $lockFile, $outputFile, $compositionFile, $composerEnvFile, $noCopyComposerEnv)
|
private static function composeNixFilePath($path)
|
||||||
|
{
|
||||||
|
if((strlen($path) > 0 && substr($path, 0, 1) === "/") || (strlen($path) > 1 && substr($path, 0, 2) === "./"))
|
||||||
|
return $path;
|
||||||
|
else
|
||||||
|
return "./".$path;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function selectSourceObject($preferredInstall, $package)
|
||||||
|
{
|
||||||
|
if($preferredInstall === "source")
|
||||||
|
{
|
||||||
|
// Use the source unless no source has been provided
|
||||||
|
if(array_key_exists("source", $package))
|
||||||
|
return $package["source"];
|
||||||
|
else if(array_key_exists("dist", $package))
|
||||||
|
return $package["dist"];
|
||||||
|
else
|
||||||
|
throw new Exception("Encountered a dangling package reference");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(array_key_exists("dist", $package))
|
||||||
|
return $package["dist"];
|
||||||
|
else
|
||||||
|
throw new Exception("Encountered a dangling package reference");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function generatePackagesExpression($outputFile, $name, $preferredInstall, array $packages)
|
||||||
|
{
|
||||||
|
$handle = fopen($outputFile, "w");
|
||||||
|
|
||||||
|
if($handle === false)
|
||||||
|
throw new Exception("Cannot write to: ".$outputFile);
|
||||||
|
|
||||||
|
fwrite($handle, "{composerEnv, fetchgit ? null}:\n\n");
|
||||||
|
fwrite($handle, "let\n");
|
||||||
|
fwrite($handle, " dependencies = {\n");
|
||||||
|
|
||||||
|
foreach($packages as $package)
|
||||||
|
{
|
||||||
|
$sourceObj = Generator::selectSourceObject($preferredInstall, $package);
|
||||||
|
|
||||||
|
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;
|
||||||
|
case "hg":
|
||||||
|
$outputStr = shell_exec('nix-prefetch-hg "'.$sourceObj['url'].'" '.$sourceObj["reference"]);
|
||||||
|
|
||||||
|
$output = json_decode($outputStr, true);
|
||||||
|
$hash = $output["sha256"];
|
||||||
|
|
||||||
|
fwrite($handle, ' "'.$package["name"].'" = fetchhg {'."\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");
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function generateCompositionExpression($compositionFile, $outputFile, $composerEnvFile)
|
||||||
|
{
|
||||||
|
$handle = fopen($compositionFile, "w");
|
||||||
|
|
||||||
|
if($handle === false)
|
||||||
|
throw new Exception("Cannot write to: ".$compositionFile);
|
||||||
|
|
||||||
|
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 ".Generator::composeNixFilePath($composerEnvFile)." {\n");
|
||||||
|
fwrite($handle, " inherit (pkgs) stdenv writeTextFile fetchurl php unzip;\n");
|
||||||
|
fwrite($handle, " };\n");
|
||||||
|
fwrite($handle, "in\n");
|
||||||
|
fwrite($handle, "import ".Generator::composeNixFilePath($outputFile)." {\n");
|
||||||
|
fwrite($handle, " inherit composerEnv;\n");
|
||||||
|
fwrite($handle, " inherit (pkgs) fetchgit;\n");
|
||||||
|
fwrite($handle, "}\n");
|
||||||
|
|
||||||
|
fclose($handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function generateNixExpressions($name, $preferredInstall, $noDev, $configFile, $lockFile, $outputFile, $compositionFile, $composerEnvFile, $noCopyComposerEnv)
|
||||||
{
|
{
|
||||||
/* Open the composer.json file and decode it */
|
/* Open the composer.json file and decode it */
|
||||||
$composerJSONStr = file_get_contents($configFile);
|
$composerJSONStr = file_get_contents($configFile);
|
||||||
|
@ -38,103 +156,30 @@ class Generator
|
||||||
throw new Exception("Cannot open contents of: ".$lockFile);
|
throw new Exception("Cannot open contents of: ".$lockFile);
|
||||||
|
|
||||||
$lockConfig = json_decode($composerLockStr, true);
|
$lockConfig = json_decode($composerLockStr, true);
|
||||||
|
|
||||||
|
if(array_key_exists("packages", $lockConfig))
|
||||||
|
$packages = $lockConfig["packages"];
|
||||||
|
else
|
||||||
|
$packages = array();
|
||||||
|
|
||||||
|
if(!$noDev && array_key_exists("packages-dev", $lockConfig))
|
||||||
|
{
|
||||||
|
foreach($lockConfig["packages-dev"] as $identifier => $devPackage)
|
||||||
|
$packages[$identifier] = $devPackage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
$lockConfig = null;
|
$packages = array();
|
||||||
|
|
||||||
/* Generate packages expression */
|
/* Generate packages expression */
|
||||||
|
Generator::generatePackagesExpression($outputFile, $name, $preferredInstall, $packages);
|
||||||
$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 */
|
/* Generate composition expression */
|
||||||
|
Generator::generateCompositionExpression($compositionFile, $outputFile, $composerEnvFile);
|
||||||
$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 */
|
/* Copy composer-env.nix */
|
||||||
|
|
||||||
if(!$noCopyComposerEnv && !copy(dirname(__FILE__)."/composer-env.nix", $composerEnvFile))
|
if(!$noCopyComposerEnv && !copy(dirname(__FILE__)."/composer-env.nix", $composerEnvFile))
|
||||||
throw new Exception("Cannot copy node-env.nix!");
|
throw new Exception("Cannot copy composer-env.nix!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
|
@ -64,7 +64,7 @@ rec {
|
||||||
|
|
||||||
if($composerLockStr === false)
|
if($composerLockStr === false)
|
||||||
{
|
{
|
||||||
print("Cannot open composer.lock contents");
|
fwrite(STDERR, "Cannot open composer.lock contents\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
Loading…
Reference in a new issue