Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

php: builder #286

Merged
merged 21 commits into from
Sep 13, 2022
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/src/subsystems/php.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ generate a dream2nix lockfile.

## Builders

None so far.
### simple (pure) (default)

Builds the package including all its dependencies in a single derivation.
1 change: 1 addition & 0 deletions src/lib/builders.nix
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
rust = "build-rust-package";
nodejs = "granular";
python = "simple-builder";
php = "simple";
};

# TODO
Expand Down
161 changes: 161 additions & 0 deletions src/subsystems/php/builders/simple/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
{...}: {
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
getAllDependencies = deps: let
getSubdependencies = dep: let
subdeps = getDependencies dep.name dep.version;
in
getAllDependencies subdeps;
in
deps ++ (l.flatten (map getSubdependencies deps));
in
getAllDependencies dependencies;

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 changes: 24 additions & 0 deletions src/subsystems/php/builders/simple/devShell.nix
Original file line number Diff line number Diff line change
@@ -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"
'';
}
31 changes: 31 additions & 0 deletions src/subsystems/php/discoverers/default/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
dlib,
lib,
subsystem,
...
}: let
l = lib // builtins;

# get translators for the project
getTranslators = path: let
nodes = l.readDir path;
in
l.optional (nodes ? "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 tree.relPath;
translators = getTranslators tree.fullPath;
subsystemInfo = {};
};
in
if l.pathExists "${tree.fullPath}/composer.json"
then [currentProjectInfo]
else [];
in {
inherit discover;
}
122 changes: 122 additions & 0 deletions src/subsystems/php/semver.nix
Original file line number Diff line number Diff line change
@@ -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;
}
16 changes: 0 additions & 16 deletions src/subsystems/php/translators/composer-json/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading