mirror of
https://github.com/nix-community/dream2nix.git
synced 2024-11-28 03:22:33 +03:00
commit
c2ebb96bf2
@ -1,11 +1,19 @@
|
||||
# PHP subsystem
|
||||
|
||||
> !!! PHP support is a work in progress, and it is not yet usable (a
|
||||
> builder is missing). You can track the progress in
|
||||
> !!! PHP support is experimental. \
|
||||
> !!! You can track the progress in
|
||||
> [nix-community/dream2nix#240](https://github.com/nix-community/dream2nix/issues/240).
|
||||
|
||||
This section documents the PHP subsystem.
|
||||
|
||||
## Example
|
||||
|
||||
An example of building [composer](https://github.com/composer/composer) using dream2nix.
|
||||
|
||||
```nix
|
||||
{{#include ../../../examples/php_composer/flake.nix}}
|
||||
```
|
||||
|
||||
## Translators
|
||||
|
||||
### composer-lock (pure)
|
||||
@ -20,4 +28,6 @@ generate a dream2nix lockfile.
|
||||
|
||||
## Builders
|
||||
|
||||
None so far.
|
||||
### simple (pure) (default)
|
||||
|
||||
Builds the package including all its dependencies in a single derivation.
|
||||
|
22
examples/php_composer/flake.nix
Normal file
22
examples/php_composer/flake.nix
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
inputs = {
|
||||
dream2nix.url = "github:nix-community/dream2nix";
|
||||
src.url = "github:composer/composer";
|
||||
src.flake = false;
|
||||
};
|
||||
|
||||
outputs = {
|
||||
self,
|
||||
dream2nix,
|
||||
src,
|
||||
} @ inp:
|
||||
(dream2nix.lib.makeFlakeOutputs {
|
||||
systems = ["x86_64-linux"];
|
||||
config.projectRoot = ./.;
|
||||
source = src;
|
||||
settings = [];
|
||||
})
|
||||
// {
|
||||
# checks = self.packages;
|
||||
};
|
||||
}
|
@ -9,6 +9,7 @@
|
||||
rust = "build-rust-package";
|
||||
nodejs = "granular";
|
||||
python = "simple-builder";
|
||||
php = "simple";
|
||||
};
|
||||
|
||||
# TODO
|
||||
|
158
src/subsystems/php/builders/simple/default.nix
Normal file
158
src/subsystems/php/builders/simple/default.nix
Normal file
@ -0,0 +1,158 @@
|
||||
{...}: {
|
||||
type = "pure";
|
||||
|
||||
build = {
|
||||
lib,
|
||||
pkgs,
|
||||
stdenv,
|
||||
# dream2nix inputs
|
||||
externals,
|
||||
...
|
||||
}: {
|
||||
### FUNCTIONS
|
||||
# AttrSet -> Bool) -> AttrSet -> [x]
|
||||
getCyclicDependencies, # name: version: -> [ {name=; version=; } ]
|
||||
getDependencies, # name: version: -> [ {name=; version=; } ]
|
||||
getSource, # name: version: -> store-path
|
||||
# to get information about the original source spec
|
||||
getSourceSpec, # name: version: -> {type="git"; url=""; hash="";}
|
||||
### ATTRIBUTES
|
||||
subsystemAttrs, # attrset
|
||||
defaultPackageName, # string
|
||||
defaultPackageVersion, # string
|
||||
# all exported (top-level) package names and versions
|
||||
# attrset of pname -> version,
|
||||
packages,
|
||||
# all existing package names and versions
|
||||
# attrset of pname -> versions,
|
||||
# where versions is a list of version strings
|
||||
packageVersions,
|
||||
# function which applies overrides to a package
|
||||
# It must be applied by the builder to each individual derivation
|
||||
# Example:
|
||||
# produceDerivation name (mkDerivation {...})
|
||||
produceDerivation,
|
||||
...
|
||||
} @ args: let
|
||||
l = lib // builtins;
|
||||
|
||||
# packages to export
|
||||
packages =
|
||||
{default = packages.${defaultPackageName};}
|
||||
// (
|
||||
l.mapAttrs
|
||||
(name: version: {"${version}" = makePackage name version;})
|
||||
args.packages
|
||||
);
|
||||
devShells =
|
||||
{default = devShells.${defaultPackageName};}
|
||||
// (
|
||||
l.mapAttrs
|
||||
(name: version: packages.${name}.${version}.devShell)
|
||||
args.packages
|
||||
);
|
||||
|
||||
# Generates a derivation for a specific package name + version
|
||||
makePackage = name: version: let
|
||||
dependencies = getDependencies name version;
|
||||
allDependencies = let
|
||||
withKey = x: x // {key = "${x.name} ${x.version}";};
|
||||
in
|
||||
l.genericClosure {
|
||||
startSet = map withKey dependencies;
|
||||
operator = dep: map withKey (getDependencies dep.name dep.version);
|
||||
};
|
||||
|
||||
intoRepository = dep: {
|
||||
type = "path";
|
||||
url = "${getSource dep.name dep.version}";
|
||||
options = {
|
||||
versions = {
|
||||
"${dep.name}" = "${dep.version}";
|
||||
};
|
||||
symlink = false;
|
||||
};
|
||||
};
|
||||
repositories = l.flatten (map intoRepository allDependencies);
|
||||
repositoriesString =
|
||||
l.toJSON
|
||||
(repositories ++ [{packagist = false;}]);
|
||||
|
||||
versionString =
|
||||
if version == "unknown"
|
||||
then "0.0.0"
|
||||
else version;
|
||||
|
||||
pkg = stdenv.mkDerivation rec {
|
||||
pname = l.strings.sanitizeDerivationName name;
|
||||
inherit version;
|
||||
|
||||
src = getSource name version;
|
||||
|
||||
nativeBuildInputs = with pkgs; [
|
||||
jq
|
||||
php81Packages.composer
|
||||
];
|
||||
buildInputs = with pkgs; [
|
||||
php81
|
||||
php81Packages.composer
|
||||
];
|
||||
|
||||
dontConfigure = true;
|
||||
buildPhase = ''
|
||||
# copy source
|
||||
PKG_OUT=$out/lib/vendor/${name}
|
||||
mkdir -p $PKG_OUT
|
||||
pushd $PKG_OUT
|
||||
cp -r ${src}/* .
|
||||
|
||||
# remove composer.lock if exists
|
||||
rm -f composer.lock
|
||||
|
||||
# disable packagist, set path repositories
|
||||
mv composer.json composer.json.orig
|
||||
|
||||
cat <<EOF >> $out/repositories.json
|
||||
${repositoriesString}
|
||||
EOF
|
||||
|
||||
jq \
|
||||
--slurpfile repositories $out/repositories.json \
|
||||
"(.repositories = \$repositories[0]) | \
|
||||
(.version = \"${versionString}\")" \
|
||||
composer.json.orig > composer.json
|
||||
|
||||
# build
|
||||
composer install --no-scripts
|
||||
|
||||
# cleanup
|
||||
rm $out/repositories.json
|
||||
popd
|
||||
'';
|
||||
installPhase = ''
|
||||
if [ -d $PKG_OUT/bin ]
|
||||
then
|
||||
mkdir -p $out/bin
|
||||
for bin in $(ls $PKG_OUT/bin)
|
||||
do
|
||||
ln -s $PKG_OUT/bin/$bin $out/bin/$bin
|
||||
done
|
||||
fi
|
||||
'';
|
||||
|
||||
passthru.devShell = import ./devShell.nix {
|
||||
inherit
|
||||
name
|
||||
pkg
|
||||
;
|
||||
inherit (pkgs) mkShell;
|
||||
php = pkgs.php81;
|
||||
};
|
||||
};
|
||||
in
|
||||
# apply packageOverrides to current derivation
|
||||
produceDerivation name pkg;
|
||||
in {
|
||||
inherit packages devShells;
|
||||
};
|
||||
}
|
24
src/subsystems/php/builders/simple/devShell.nix
Normal file
24
src/subsystems/php/builders/simple/devShell.nix
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
name,
|
||||
pkg,
|
||||
mkShell,
|
||||
php,
|
||||
}:
|
||||
mkShell {
|
||||
buildInputs = [
|
||||
php
|
||||
];
|
||||
shellHook = let
|
||||
vendorDir =
|
||||
pkg.overrideAttrs (old: {
|
||||
dontInstall = true;
|
||||
})
|
||||
+ "/lib/vendor/${name}/vendor";
|
||||
in ''
|
||||
rm -rf ./vendor
|
||||
mkdir vendor
|
||||
cp -r ${vendorDir}/* vendor/
|
||||
chmod -R +w ./vendor
|
||||
export PATH="$PATH:$(realpath ./vendor)/bin"
|
||||
'';
|
||||
}
|
35
src/subsystems/php/discoverers/default/default.nix
Normal file
35
src/subsystems/php/discoverers/default/default.nix
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
dlib,
|
||||
lib,
|
||||
subsystem,
|
||||
...
|
||||
}: let
|
||||
l = lib // builtins;
|
||||
|
||||
# get translators for the project
|
||||
getTranslators = tree:
|
||||
l.optional (tree.files ? "composer.lock") "composer-lock"
|
||||
++ ["composer-json"];
|
||||
|
||||
# discover php projects
|
||||
discover = {tree}: let
|
||||
currentProjectInfo = dlib.construct.discoveredProject {
|
||||
inherit subsystem;
|
||||
inherit (tree) relPath;
|
||||
name =
|
||||
tree.files."composer.json".jsonContent.name
|
||||
or (
|
||||
if tree.relPath != ""
|
||||
then tree.relPath
|
||||
else "unknown"
|
||||
);
|
||||
translators = getTranslators tree;
|
||||
subsystemInfo = {};
|
||||
};
|
||||
in
|
||||
if l.pathExists "${tree.fullPath}/composer.json"
|
||||
then [currentProjectInfo]
|
||||
else [];
|
||||
in {
|
||||
inherit discover;
|
||||
}
|
122
src/subsystems/php/semver.nix
Normal file
122
src/subsystems/php/semver.nix
Normal file
@ -0,0 +1,122 @@
|
||||
{lib}: let
|
||||
l = lib // builtins;
|
||||
|
||||
# Replace a list entry at defined index with set value
|
||||
ireplace = idx: value: list:
|
||||
l.genList (i:
|
||||
if i == idx
|
||||
then value
|
||||
else (l.elemAt list i)) (l.length list);
|
||||
|
||||
orBlank = x:
|
||||
if x != null
|
||||
then x
|
||||
else "";
|
||||
|
||||
operators = let
|
||||
mkComparison = ret: version: v:
|
||||
builtins.compareVersions version v == ret;
|
||||
|
||||
mkCaretComparison = version: v: let
|
||||
ver = builtins.splitVersion v;
|
||||
major = l.toInt (l.head ver);
|
||||
minor = builtins.toString (l.toInt (l.head ver) + 1);
|
||||
upper = builtins.concatStringsSep "." (ireplace 0 minor ver);
|
||||
in
|
||||
if major == 0
|
||||
then mkTildeComparison version v
|
||||
else operators.">=" version v && operators."<" version upper;
|
||||
|
||||
mkTildeComparison = version: v: let
|
||||
ver = builtins.splitVersion v;
|
||||
len = l.length ver;
|
||||
truncated =
|
||||
if len > 1
|
||||
then l.init ver
|
||||
else ver;
|
||||
idx = (l.length truncated) - 1;
|
||||
minor = l.toString (l.toInt (l.elemAt truncated idx) + 1);
|
||||
upper = l.concatStringsSep "." (ireplace idx minor truncated);
|
||||
in
|
||||
operators.">=" version v && operators."<" version upper;
|
||||
in {
|
||||
# Prefix operators
|
||||
"==" = mkComparison 0;
|
||||
">" = mkComparison 1;
|
||||
"<" = mkComparison (-1);
|
||||
"!=" = v: c: !operators."==" v c;
|
||||
">=" = v: c: operators."==" v c || operators.">" v c;
|
||||
"<=" = v: c: operators."==" v c || operators."<" v c;
|
||||
# Semver specific operators
|
||||
"~" = mkTildeComparison;
|
||||
"^" = mkCaretComparison;
|
||||
};
|
||||
|
||||
re = {
|
||||
operators = "([=><!~^]+)";
|
||||
version = "((0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)|(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)|(0|[1-9][0-9]*)){0,1}([.x*]*)(-((0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*)){0,1}(\\+([0-9a-zA-Z-]+(\\.[0-9a-zA-Z-]+)*)){0,1}";
|
||||
};
|
||||
|
||||
reLengths = {
|
||||
operators = 1;
|
||||
version = 16;
|
||||
};
|
||||
|
||||
parseConstraint = constraintStr: let
|
||||
# The common prefix operators
|
||||
mPre = l.match "${re.operators} *${re.version}" constraintStr;
|
||||
# There is an upper bound to the operator (this implementation is a bit hacky)
|
||||
mUpperBound =
|
||||
l.match "${re.operators} *${re.version} *< *${re.version}" constraintStr;
|
||||
# There is also an infix operator to match ranges
|
||||
mIn = l.match "${re.version} - *${re.version}" constraintStr;
|
||||
# There is no operators
|
||||
mNone = l.match "${re.version}" constraintStr;
|
||||
in (
|
||||
if mPre != null
|
||||
then {
|
||||
ops.t = l.elemAt mPre 0;
|
||||
v = orBlank (l.elemAt mPre reLengths.operators);
|
||||
}
|
||||
# Infix operators are range matches
|
||||
else if mIn != null
|
||||
then {
|
||||
ops = {
|
||||
t = "-";
|
||||
l = ">=";
|
||||
u = "<=";
|
||||
};
|
||||
v = {
|
||||
vl = orBlank (l.elemAt mIn 0);
|
||||
vu = orBlank (l.elemAt mIn reLengths.version);
|
||||
};
|
||||
}
|
||||
else if mUpperBound != null
|
||||
then {
|
||||
ops = {
|
||||
t = "-";
|
||||
l = l.elemAt mUpperBound 0;
|
||||
u = "<";
|
||||
};
|
||||
v = {
|
||||
vl = orBlank (l.elemAt mUpperBound reLengths.operators);
|
||||
vu = orBlank (l.elemAt mUpperBound (reLengths.operators + reLengths.version));
|
||||
};
|
||||
}
|
||||
else if mNone != null
|
||||
then {
|
||||
ops.t = "==";
|
||||
v = orBlank (l.elemAt mNone 0);
|
||||
}
|
||||
else throw ''Constraint "${constraintStr}" could not be parsed''
|
||||
);
|
||||
|
||||
satisfies = version: constraint: let
|
||||
inherit (parseConstraint constraint) ops v;
|
||||
in
|
||||
if ops.t == "-"
|
||||
then (operators."${ops.l}" version v.vl && operators."${ops.u}" version v.vu)
|
||||
else operators."${ops.t}" version v;
|
||||
in {
|
||||
inherit satisfies;
|
||||
}
|
@ -7,22 +7,6 @@
|
||||
in {
|
||||
type = "impure";
|
||||
|
||||
/*
|
||||
Allow dream2nix to detect if a given directory contains a project
|
||||
which can be translated with this translator.
|
||||
Usually this can be done by checking for the existence of specific
|
||||
file names or file endings.
|
||||
|
||||
Alternatively a fully featured discoverer can be implemented under
|
||||
`src/subsystems/{subsystem}/discoverers`.
|
||||
This is recommended if more complex project structures need to be
|
||||
discovered like, for example, workspace projects spanning over multiple
|
||||
sub-directories
|
||||
|
||||
If a fully featured discoverer exists, do not define `discoverProject`.
|
||||
*/
|
||||
discoverProject = tree: (l.pathExists "${tree.fullPath}/composer.json");
|
||||
|
||||
# A derivation which outputs a single executable at `$out`.
|
||||
# The executable will be called by dream2nix for translation
|
||||
# The input format is specified in /specifications/translator-call-example.json.
|
||||
|
@ -22,24 +22,6 @@ in {
|
||||
})
|
||||
];
|
||||
|
||||
/*
|
||||
Allow dream2nix to detect if a given directory contains a project
|
||||
which can be translated with this translator.
|
||||
Usually this can be done by checking for the existence of specific
|
||||
file names or file endings.
|
||||
|
||||
Alternatively a fully featured discoverer can be implemented under
|
||||
`src/subsystems/{subsystem}/discoverers`.
|
||||
This is recommended if more complex project structures need to be
|
||||
discovered like, for example, workspace projects spanning over multiple
|
||||
sub-directories
|
||||
|
||||
If a fully featured discoverer exists, do not define `discoverProject`.
|
||||
*/
|
||||
discoverProject = tree:
|
||||
(l.pathExists "${tree.fullPath}/composer.json")
|
||||
&& (l.pathExists "${tree.fullPath}/composer.lock");
|
||||
|
||||
# translate from a given source and a project specification to a dream-lock.
|
||||
translate = {
|
||||
translatorName,
|
||||
@ -189,9 +171,20 @@ in {
|
||||
};
|
||||
doReplace = pkg: l.foldl replace pkg packages;
|
||||
doProvide = pkg: l.foldl provide pkg packages;
|
||||
resolve = pkg: doProvide (doReplace pkg);
|
||||
dropMissing = pkgs: let
|
||||
doDropMissing = pkg:
|
||||
pkg
|
||||
// {
|
||||
require =
|
||||
l.filterAttrs
|
||||
(name: semver: l.any (pkg: (pkg.name == name) && (satisfiesSemver pkg.version semver)) pkgs)
|
||||
(getDependencies pkg);
|
||||
};
|
||||
in
|
||||
map doDropMissing pkgs;
|
||||
resolve = pkg: (doProvide (doReplace pkg));
|
||||
in
|
||||
map resolve packages;
|
||||
dropMissing (map resolve packages);
|
||||
|
||||
# toplevel php semver
|
||||
phpSemver = composerJson.require."php" or "*";
|
||||
@ -204,7 +197,13 @@ in {
|
||||
map (l.strings.removePrefix "ext-") (l.lists.unique extensions);
|
||||
|
||||
# get dependencies
|
||||
getDependencies = pkg: (pkg.require or {});
|
||||
getDependencies = pkg:
|
||||
l.mapAttrs
|
||||
(name: version:
|
||||
if version == "self.version"
|
||||
then pkg.version
|
||||
else version)
|
||||
(pkg.require or {});
|
||||
|
||||
# resolve semvers into exact versions
|
||||
pinPackages = pkgs: let
|
||||
@ -246,7 +245,7 @@ in {
|
||||
};
|
||||
|
||||
# name of the default package
|
||||
defaultPackage = composerJson.name;
|
||||
defaultPackage = project.name;
|
||||
|
||||
/*
|
||||
List the package candidates which should be exposed to the user.
|
||||
@ -254,7 +253,7 @@ in {
|
||||
Users will not be interested in all individual dependencies.
|
||||
*/
|
||||
exportedPackages = {
|
||||
"${defaultPackage}" = composerJson.version or "0.0.0";
|
||||
"${defaultPackage}" = composerJson.version or "unknown";
|
||||
};
|
||||
|
||||
/*
|
||||
@ -276,7 +275,7 @@ in {
|
||||
(
|
||||
if noDev
|
||||
then {}
|
||||
else composerJson.require-dev
|
||||
else composerJson.require-dev or {}
|
||||
)
|
||||
// composerJson.require;
|
||||
}
|
||||
|
@ -1,10 +1,8 @@
|
||||
{
|
||||
utils,
|
||||
lib,
|
||||
...
|
||||
}: let
|
||||
{lib, ...}: let
|
||||
l = lib // builtins;
|
||||
|
||||
inherit (import ./semver.nix {inherit lib;}) satisfies;
|
||||
|
||||
# composer.lock uses a less strict semver interpretation
|
||||
# ~1.2 -> >=1.2 <2.0.0 (instead of >=1.2.0 <1.3.0)
|
||||
# ~1 -> >=1.0 <2.0.0
|
||||
@ -35,7 +33,7 @@
|
||||
then "^${l.head m}"
|
||||
else c;
|
||||
removeV = c: let
|
||||
m = l.match "^(.)v([[:d:]]+[.].*)$" c;
|
||||
m = l.match "^(.)*v([[:d:]]+[.].*)$" c;
|
||||
in
|
||||
if m != null && l.length m > 0
|
||||
then l.concatStrings m
|
||||
@ -43,24 +41,30 @@
|
||||
cleanConstraint = removeV (wildcard (tilde (removeSuffix constraint)));
|
||||
cleanVersion = removeX (l.removePrefix "v" (removeSuffix version));
|
||||
in
|
||||
(version == constraint)
|
||||
|| (
|
||||
utils.satisfiesSemver
|
||||
cleanVersion
|
||||
cleanConstraint
|
||||
);
|
||||
(l.any (x: constraint == x) ["*" "@dev" "@master" "@dev-master"])
|
||||
|| (version == constraint)
|
||||
|| (satisfies cleanVersion cleanConstraint);
|
||||
|
||||
trim = s: l.head (l.match "^[[:space:]]*(.*[^[:space:]])[[:space:]]*$" s);
|
||||
splitAlternatives = v: let
|
||||
# handle version alternatives: ^1.2 || ^2.0
|
||||
trim = s: l.head (l.match "^[[:space:]]*([^[:space:]]*)[[:space:]]*$" s);
|
||||
clean = l.replaceStrings ["||"] ["|"] v;
|
||||
in
|
||||
map trim (l.splitString "|" clean);
|
||||
splitConjunctives = v: let
|
||||
clean = l.replaceStrings ["," " - " " -" "- "] [" " "-" "-" "-"] v;
|
||||
in
|
||||
map trim (l.splitString " " clean);
|
||||
in {
|
||||
# 1.0.2 ~1.0.1
|
||||
# matching a version with semver
|
||||
satisfiesSemver = version: constraint:
|
||||
l.any (satisfiesSemverSingle version) (splitAlternatives constraint);
|
||||
l.any
|
||||
(c:
|
||||
l.all
|
||||
(satisfiesSemverSingle version)
|
||||
(splitConjunctives c))
|
||||
(splitAlternatives constraint);
|
||||
|
||||
# 1.0|2.0 ^2.0
|
||||
# matching multiversion like the one in `provide` with semver
|
||||
@ -68,6 +72,6 @@ in {
|
||||
satisfies = v: c: (v == "") || (v == "*") || (satisfiesSemverSingle v c);
|
||||
in
|
||||
l.any
|
||||
(c: l.any (v: satisfies v c) (splitAlternatives multiversion))
|
||||
(c: l.any (v: l.all (satisfies v) (splitConjunctives c)) (splitAlternatives multiversion))
|
||||
(splitAlternatives constraint);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user