mirror of
https://github.com/nix-community/dream2nix.git
synced 2024-12-24 15:01:56 +03:00
Merge pull request #262 from tinybeachthor/php-translator-composer-json
php: impure translator `composer-json` + fix `composer-lock`
This commit is contained in:
commit
ab769ee266
@ -10,6 +10,7 @@
|
|||||||
- [Python](./subsystems/python.md)
|
- [Python](./subsystems/python.md)
|
||||||
- [Node.js](./subsystems/node.md)
|
- [Node.js](./subsystems/node.md)
|
||||||
- [Haskell](./subsystems/haskell.md)
|
- [Haskell](./subsystems/haskell.md)
|
||||||
|
- [PHP](./subsystems/php.md)
|
||||||
|
|
||||||
# Concepts
|
# Concepts
|
||||||
- [Architectural Considerations](./intro/architectural-considerations.md)
|
- [Architectural Considerations](./intro/architectural-considerations.md)
|
||||||
|
23
docs/src/subsystems/php.md
Normal file
23
docs/src/subsystems/php.md
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# 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
|
||||||
|
> [nix-community/dream2nix#240](https://github.com/nix-community/dream2nix/issues/240).
|
||||||
|
|
||||||
|
This section documents the PHP subsystem.
|
||||||
|
|
||||||
|
## Translators
|
||||||
|
|
||||||
|
### composer-lock (pure)
|
||||||
|
|
||||||
|
Translates `composer.lock` into a dream2nix lockfile.
|
||||||
|
|
||||||
|
### composer-json (impure)
|
||||||
|
|
||||||
|
Resolves dependencies in `composer.json` using `composer` to generate a
|
||||||
|
`composer.lock` lockfile, then invokes the `composer-lock` translator to
|
||||||
|
generate a dream2nix lockfile.
|
||||||
|
|
||||||
|
## Builders
|
||||||
|
|
||||||
|
None so far.
|
93
src/subsystems/php/translators/composer-json/default.nix
Normal file
93
src/subsystems/php/translators/composer-json/default.nix
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
{
|
||||||
|
dlib,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
l = lib // builtins;
|
||||||
|
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.
|
||||||
|
# The first arg `$1` will be a json file containing the input parameters
|
||||||
|
# like defined in /src/specifications/translator-call-example.json and the
|
||||||
|
# additional arguments required according to extraArgs
|
||||||
|
#
|
||||||
|
# The program is expected to create a file at the location specified
|
||||||
|
# by the input parameter `outFile`.
|
||||||
|
# The output file must contain the dream lock data encoded as json.
|
||||||
|
# See /src/specifications/dream-lock-example.json
|
||||||
|
translateBin = {
|
||||||
|
# dream2nix utils
|
||||||
|
subsystems,
|
||||||
|
utils,
|
||||||
|
# nixpkgs dependenies
|
||||||
|
bash,
|
||||||
|
coreutils,
|
||||||
|
jq,
|
||||||
|
phpPackages,
|
||||||
|
writeScriptBin,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
utils.writePureShellScript
|
||||||
|
[
|
||||||
|
bash
|
||||||
|
coreutils
|
||||||
|
jq
|
||||||
|
phpPackages.composer
|
||||||
|
]
|
||||||
|
''
|
||||||
|
# accroding to the spec, the translator reads the input from a json file
|
||||||
|
jsonInput=$1
|
||||||
|
|
||||||
|
# read the json input
|
||||||
|
outputFile=$(realpath -m $(jq '.outputFile' -c -r $jsonInput))
|
||||||
|
source=$(jq '.source' -c -r $jsonInput)
|
||||||
|
relPath=$(jq '.project.relPath' -c -r $jsonInput)
|
||||||
|
|
||||||
|
pushd $TMPDIR
|
||||||
|
cp -r $source/* ./
|
||||||
|
chmod -R +w ./
|
||||||
|
newSource=$(pwd)
|
||||||
|
|
||||||
|
cd ./$relPath
|
||||||
|
rm -f composer.lock
|
||||||
|
|
||||||
|
echo "translating in temp dir: $(pwd)"
|
||||||
|
|
||||||
|
# create lockfile
|
||||||
|
if [ "$(jq '.project.subsystemInfo.noDev' -c -r $jsonInput)" == "true" ]; then
|
||||||
|
echo "excluding dev dependencies"
|
||||||
|
jq '.require-dev = {}' ./composer.json > composer.json.mod
|
||||||
|
mv composer.json.mod composer.json
|
||||||
|
composer update --no-install --no-dev
|
||||||
|
else
|
||||||
|
composer update --no-install
|
||||||
|
fi
|
||||||
|
|
||||||
|
jq ".source = \"$newSource\"" -c -r $jsonInput > $TMPDIR/newJsonInput
|
||||||
|
|
||||||
|
popd
|
||||||
|
${subsystems.php.translators.composer-lock.translateBin} $TMPDIR/newJsonInput
|
||||||
|
'';
|
||||||
|
|
||||||
|
# inherit options from composer-lock translator
|
||||||
|
extraArgs = dlib.translators.translators.php.composer-lock.extraArgs;
|
||||||
|
}
|
@ -17,8 +17,8 @@ in {
|
|||||||
*/
|
*/
|
||||||
generateUnitTestsForProjects = [
|
generateUnitTestsForProjects = [
|
||||||
(builtins.fetchTarball {
|
(builtins.fetchTarball {
|
||||||
url = "https://code.castopod.org/adaures/castopod/-/archive/v1.0.0-alpha.80/castopod-v1.0.0-alpha.80.tar.gz";
|
url = "https://github.com/tinybeachthor/dream2nix-php-composer-lock/archive/refs/tags/complex.tar.gz";
|
||||||
sha256 = "sha256:0lv75pxzhs6q9w22czbgbnc48n6zhaajw9bag2sscaqnvfvfhcsf";
|
sha256 = "sha256:1xa5paafhwv4bcn2jsmbp1v2afh729r2h153g871zxdmsxsgwrn1";
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -120,59 +120,113 @@ in {
|
|||||||
composerJson = (projectTree.getNodeFromPath "composer.json").jsonContent;
|
composerJson = (projectTree.getNodeFromPath "composer.json").jsonContent;
|
||||||
composerLock = (projectTree.getNodeFromPath "composer.lock").jsonContent;
|
composerLock = (projectTree.getNodeFromPath "composer.lock").jsonContent;
|
||||||
|
|
||||||
inherit (callPackageDream ../../utils.nix {}) satisfiesSemver;
|
inherit
|
||||||
|
(callPackageDream ../../utils.nix {})
|
||||||
|
satisfiesSemver
|
||||||
|
multiSatisfiesSemver
|
||||||
|
;
|
||||||
|
|
||||||
# all the pinned packages
|
# all the packages
|
||||||
packages =
|
packages =
|
||||||
composerLock.packages
|
composerLock.packages
|
||||||
++ (
|
++ (
|
||||||
if noDev
|
if noDev
|
||||||
then []
|
then []
|
||||||
else composerLock."packages-dev"
|
else composerLock.packages-dev
|
||||||
);
|
);
|
||||||
|
|
||||||
|
# packages with replacements applied
|
||||||
|
resolvedPackages = let
|
||||||
|
getProvide = pkg: (pkg.provide or {});
|
||||||
|
getReplace = pkg: let
|
||||||
|
resolveVersion = _: version:
|
||||||
|
if version == "self.version"
|
||||||
|
then pkg.version
|
||||||
|
else version;
|
||||||
|
in
|
||||||
|
l.mapAttrs resolveVersion (pkg.replace or {});
|
||||||
|
provide = pkg: dep: let
|
||||||
|
requirements = getDependencies pkg;
|
||||||
|
providements = getProvide dep;
|
||||||
|
cleanRequirements =
|
||||||
|
l.filterAttrs (
|
||||||
|
name: semver:
|
||||||
|
!((providements ? "${name}")
|
||||||
|
&& (multiSatisfiesSemver providements."${name}" semver))
|
||||||
|
)
|
||||||
|
requirements;
|
||||||
|
in
|
||||||
|
pkg
|
||||||
|
// {
|
||||||
|
require =
|
||||||
|
cleanRequirements
|
||||||
|
// (
|
||||||
|
if requirements != cleanRequirements
|
||||||
|
then {"${dep.name}" = "${dep.version}";}
|
||||||
|
else {}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
replace = pkg: dep: let
|
||||||
|
requirements = getDependencies pkg;
|
||||||
|
replacements = getReplace dep;
|
||||||
|
cleanRequirements =
|
||||||
|
l.filterAttrs (
|
||||||
|
name: semver:
|
||||||
|
!((replacements ? "${name}")
|
||||||
|
&& (satisfiesSemver replacements."${name}" semver))
|
||||||
|
)
|
||||||
|
requirements;
|
||||||
|
in
|
||||||
|
pkg
|
||||||
|
// {
|
||||||
|
require =
|
||||||
|
cleanRequirements
|
||||||
|
// (
|
||||||
|
if requirements != cleanRequirements
|
||||||
|
then {"${dep.name}" = "${dep.version}";}
|
||||||
|
else {}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
doReplace = pkg: l.foldl replace pkg packages;
|
||||||
|
doProvide = pkg: l.foldl provide pkg packages;
|
||||||
|
resolve = pkg: doProvide (doReplace pkg);
|
||||||
|
in
|
||||||
|
map resolve packages;
|
||||||
|
|
||||||
# toplevel php semver
|
# toplevel php semver
|
||||||
phpSemver = composerJson.require."php";
|
phpSemver = composerJson.require."php" or "*";
|
||||||
# all the php extensions
|
# all the php extensions
|
||||||
phpExtensions = let
|
phpExtensions = let
|
||||||
all = map (pkg: l.attrsets.attrNames (getRequire pkg)) packages;
|
all = map (pkg: l.attrsets.attrNames (getDependencies pkg)) resolvedPackages;
|
||||||
flat = l.lists.flatten all;
|
flat = l.lists.flatten all;
|
||||||
extensions = l.filter (l.strings.hasPrefix "ext-") flat;
|
extensions = l.filter (l.strings.hasPrefix "ext-") flat;
|
||||||
in
|
in
|
||||||
l.lists.unique extensions;
|
map (l.strings.removePrefix "ext-") (l.lists.unique extensions);
|
||||||
|
|
||||||
# get require (and require-dev)
|
# get dependencies
|
||||||
getRequire = pkg:
|
getDependencies = pkg: (pkg.require or {});
|
||||||
(
|
|
||||||
if noDev
|
|
||||||
then []
|
|
||||||
else (pkg."require-dev" or {})
|
|
||||||
)
|
|
||||||
// (pkg.require or {});
|
|
||||||
# strip php version & php extensions
|
|
||||||
cleanRequire = deps:
|
|
||||||
l.filterAttrs
|
|
||||||
(name: _: (name != "php") && !(l.strings.hasPrefix "ext-" name))
|
|
||||||
deps;
|
|
||||||
|
|
||||||
# resolve semvers into exact versions
|
# resolve semvers into exact versions
|
||||||
pinRequires = dep: let
|
pinPackages = pkgs: let
|
||||||
pin = name: semver:
|
clean = requires:
|
||||||
|
l.filterAttrs
|
||||||
|
(name: _:
|
||||||
|
(l.all (x: name != x) ["php" "composer/composer" "composer-runtime-api"])
|
||||||
|
&& !(l.strings.hasPrefix "ext-" name))
|
||||||
|
requires;
|
||||||
|
doPin = name: semver:
|
||||||
(l.head
|
(l.head
|
||||||
(l.filter (dep: satisfiesSemver dep.version semver)
|
(l.filter (dep: satisfiesSemver dep.version semver)
|
||||||
(l.filter (dep: dep.name == name)
|
(l.filter (dep: dep.name == name)
|
||||||
packages)))
|
resolvedPackages)))
|
||||||
.version;
|
.version;
|
||||||
pinAttr = attr:
|
doPins = pkg:
|
||||||
if attr ? dep
|
pkg
|
||||||
then l.mapAttrs pin dep."${attr}"
|
// {
|
||||||
else {};
|
require = l.mapAttrs doPin (clean pkg.require);
|
||||||
|
};
|
||||||
in
|
in
|
||||||
dep
|
map doPins pkgs;
|
||||||
// {
|
|
||||||
require = pinAttr "require";
|
|
||||||
"require-dev" = pinAttr "require-dev";
|
|
||||||
};
|
|
||||||
in
|
in
|
||||||
dlib.simpleTranslate2.translate
|
dlib.simpleTranslate2.translate
|
||||||
({objectsByKey, ...}: rec {
|
({objectsByKey, ...}: rec {
|
||||||
@ -200,7 +254,7 @@ in {
|
|||||||
Users will not be interested in all individual dependencies.
|
Users will not be interested in all individual dependencies.
|
||||||
*/
|
*/
|
||||||
exportedPackages = {
|
exportedPackages = {
|
||||||
"${defaultPackage}" = composerJson.version;
|
"${defaultPackage}" = composerJson.version or "0.0.0";
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -208,9 +262,8 @@ in {
|
|||||||
If the upstream format is a deep attrset, this list should contain
|
If the upstream format is a deep attrset, this list should contain
|
||||||
a flattened representation of all entries.
|
a flattened representation of all entries.
|
||||||
*/
|
*/
|
||||||
serializedRawObjects =
|
serializedRawObjects = pinPackages (
|
||||||
(map pinRequires composerLock.packages)
|
[
|
||||||
++ [
|
|
||||||
# Add the top-level package, this is not written in composer.lock
|
# Add the top-level package, this is not written in composer.lock
|
||||||
{
|
{
|
||||||
name = defaultPackage;
|
name = defaultPackage;
|
||||||
@ -219,10 +272,17 @@ in {
|
|||||||
type = "path";
|
type = "path";
|
||||||
path = projectSource;
|
path = projectSource;
|
||||||
};
|
};
|
||||||
require = (pinRequires composerJson).require;
|
require =
|
||||||
"require-dev" = (pinRequires composerJson)."require-dev";
|
(
|
||||||
|
if noDev
|
||||||
|
then {}
|
||||||
|
else composerJson.require-dev
|
||||||
|
)
|
||||||
|
// composerJson.require;
|
||||||
}
|
}
|
||||||
];
|
]
|
||||||
|
++ resolvedPackages
|
||||||
|
);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Define extractor functions which each extract one property from
|
Define extractor functions which each extract one property from
|
||||||
@ -242,7 +302,7 @@ in {
|
|||||||
dependencies = rawObj: finalObj:
|
dependencies = rawObj: finalObj:
|
||||||
l.attrsets.mapAttrsToList
|
l.attrsets.mapAttrsToList
|
||||||
(name: version: {inherit name version;})
|
(name: version: {inherit name version;})
|
||||||
(cleanRequire (getRequire rawObj));
|
(getDependencies rawObj);
|
||||||
|
|
||||||
sourceSpec = rawObj: finalObj:
|
sourceSpec = rawObj: finalObj:
|
||||||
if rawObj.source.type == "path"
|
if rawObj.source.type == "path"
|
||||||
|
@ -1,14 +1,73 @@
|
|||||||
{utils, ...}: {
|
{
|
||||||
|
utils,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
l = lib // builtins;
|
||||||
|
|
||||||
# composer.lock uses a less strict semver interpretation
|
# composer.lock uses a less strict semver interpretation
|
||||||
# ~1.2 -> >=1.2 <2.0.0 (instead of >=1.2.0 <1.3.0)
|
# ~1.2 -> >=1.2 <2.0.0 (instead of >=1.2.0 <1.3.0)
|
||||||
|
# ~1 -> >=1.0 <2.0.0
|
||||||
# this is identical with ^1.2 in the semver standard
|
# this is identical with ^1.2 in the semver standard
|
||||||
satisfiesSemver = version: constraint: let
|
#
|
||||||
minorTilde = l.match "^[~]([[:d:]]+[.][[:d:]]+)$" constraint;
|
# remove v from version strings: ^v1.2.3 -> ^1.2.3
|
||||||
cleanConstraint =
|
#
|
||||||
if minorTilde != null && l.length minorTilde >= 0
|
# remove branch suffix: ^1.2.x-dev -> ^1.2
|
||||||
then "^${l.head minorTilde}"
|
#
|
||||||
else constraint;
|
satisfiesSemverSingle = version: constraint: let
|
||||||
cleanVersion = l.removePrefix "v" version;
|
removeSuffix = c: let
|
||||||
|
m = l.match "^(.*)[-][[:alpha:]]+$" c;
|
||||||
|
in
|
||||||
|
if m != null && l.length m >= 0
|
||||||
|
then l.head m
|
||||||
|
else c;
|
||||||
|
removeX = l.strings.removeSuffix ".x";
|
||||||
|
tilde = c: let
|
||||||
|
m = l.match "^[~]([[:d:]]+.*)$" c;
|
||||||
|
in
|
||||||
|
if m != null && l.length m >= 0
|
||||||
|
then "^${l.head m}"
|
||||||
|
else c;
|
||||||
|
wildcard = c: let
|
||||||
|
m = l.match "^([[:d:]]+.*)[.][*x]$" c;
|
||||||
|
in
|
||||||
|
if m != null && l.length m >= 0
|
||||||
|
then "^${l.head m}"
|
||||||
|
else c;
|
||||||
|
removeV = c: let
|
||||||
|
m = l.match "^(.)v([[:d:]]+[.].*)$" c;
|
||||||
|
in
|
||||||
|
if m != null && l.length m > 0
|
||||||
|
then l.concatStrings m
|
||||||
|
else c;
|
||||||
|
cleanConstraint = removeV (wildcard (tilde (removeSuffix constraint)));
|
||||||
|
cleanVersion = removeX (l.removePrefix "v" (removeSuffix version));
|
||||||
in
|
in
|
||||||
utils.satisfiesSemver cleanVersion cleanConstraint;
|
(version == constraint)
|
||||||
|
|| (
|
||||||
|
utils.satisfiesSemver
|
||||||
|
cleanVersion
|
||||||
|
cleanConstraint
|
||||||
|
);
|
||||||
|
|
||||||
|
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);
|
||||||
|
in {
|
||||||
|
# 1.0.2 ~1.0.1
|
||||||
|
# matching a version with semver
|
||||||
|
satisfiesSemver = version: constraint:
|
||||||
|
l.any (satisfiesSemverSingle version) (splitAlternatives constraint);
|
||||||
|
|
||||||
|
# 1.0|2.0 ^2.0
|
||||||
|
# matching multiversion like the one in `provide` with semver
|
||||||
|
multiSatisfiesSemver = multiversion: constraint: let
|
||||||
|
satisfies = v: c: (v == "") || (v == "*") || (satisfiesSemverSingle v c);
|
||||||
|
in
|
||||||
|
l.any
|
||||||
|
(c: l.any (v: satisfies v c) (splitAlternatives multiversion))
|
||||||
|
(splitAlternatives constraint);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user