diff options
Diffstat (limited to 'pkgs/build-support/php/builders')
7 files changed, 571 insertions, 0 deletions
diff --git a/pkgs/build-support/php/builders/default.nix b/pkgs/build-support/php/builders/default.nix new file mode 100644 index 0000000000000..209c834367d18 --- /dev/null +++ b/pkgs/build-support/php/builders/default.nix @@ -0,0 +1,8 @@ +{ callPackage, callPackages, ... }: +{ + v1 = { + buildComposerProject = callPackage ./v1/build-composer-project.nix { }; + mkComposerRepository = callPackage ./v1/build-composer-repository.nix { }; + composerHooks = callPackages ./v1/hooks { }; + }; +} diff --git a/pkgs/build-support/php/builders/v1/build-composer-project.nix b/pkgs/build-support/php/builders/v1/build-composer-project.nix new file mode 100644 index 0000000000000..a292335932547 --- /dev/null +++ b/pkgs/build-support/php/builders/v1/build-composer-project.nix @@ -0,0 +1,103 @@ +{ + callPackage, + stdenvNoCC, + lib, + php, +}: + +let + buildComposerProjectOverride = + finalAttrs: previousAttrs: + + let + phpDrv = finalAttrs.php or php; + composer = finalAttrs.composer or phpDrv.packages.composer; + composer-local-repo-plugin = callPackage ../../pkgs/composer-local-repo-plugin.nix { }; + in + { + composerLock = previousAttrs.composerLock or null; + composerNoDev = previousAttrs.composerNoDev or true; + composerNoPlugins = previousAttrs.composerNoPlugins or true; + composerNoScripts = previousAttrs.composerNoScripts or true; + composerStrictValidation = previousAttrs.composerStrictValidation or true; + + nativeBuildInputs = (previousAttrs.nativeBuildInputs or [ ]) ++ [ + composer + composer-local-repo-plugin + phpDrv + phpDrv.composerHooks.composerInstallHook + ]; + + buildInputs = (previousAttrs.buildInputs or [ ]) ++ [ phpDrv ]; + + patches = previousAttrs.patches or [ ]; + strictDeps = previousAttrs.strictDeps or true; + + # Should we keep these empty phases? + configurePhase = + previousAttrs.configurePhase or '' + runHook preConfigure + + runHook postConfigure + ''; + + buildPhase = + previousAttrs.buildPhase or '' + runHook preBuild + + runHook postBuild + ''; + + doCheck = previousAttrs.doCheck or true; + checkPhase = + previousAttrs.checkPhase or '' + runHook preCheck + + runHook postCheck + ''; + + installPhase = + previousAttrs.installPhase or '' + runHook preInstall + + runHook postInstall + ''; + + doInstallCheck = previousAttrs.doInstallCheck or false; + installCheckPhase = + previousAttrs.installCheckPhase or '' + runHook preInstallCheck + + runHook postInstallCheck + ''; + + composerRepository = + previousAttrs.composerRepository or (phpDrv.mkComposerRepository { + inherit composer composer-local-repo-plugin; + inherit (finalAttrs) + patches + pname + src + vendorHash + version + ; + + composerLock = previousAttrs.composerLock or null; + composerNoDev = previousAttrs.composerNoDev or true; + composerNoPlugins = previousAttrs.composerNoPlugins or true; + composerNoScripts = previousAttrs.composerNoScripts or true; + composerStrictValidation = previousAttrs.composerStrictValidation or true; + }); + + env = { + COMPOSER_CACHE_DIR = "/dev/null"; + COMPOSER_DISABLE_NETWORK = "1"; + COMPOSER_MIRROR_PATH_REPOS = "1"; + }; + + meta = previousAttrs.meta or { } // { + platforms = lib.platforms.all; + }; + }; +in +args: (stdenvNoCC.mkDerivation args).overrideAttrs buildComposerProjectOverride diff --git a/pkgs/build-support/php/builders/v1/build-composer-repository.nix b/pkgs/build-support/php/builders/v1/build-composer-repository.nix new file mode 100644 index 0000000000000..54944b91d202d --- /dev/null +++ b/pkgs/build-support/php/builders/v1/build-composer-repository.nix @@ -0,0 +1,121 @@ +{ + callPackage, + stdenvNoCC, + lib, + php, +}: + +let + mkComposerRepositoryOverride = + /* + We cannot destruct finalAttrs since the attrset below is used to construct it + and Nix currently does not support lazy attribute names. + { + php ? null, + composer ? null, + composerLock ? "composer.lock", + src, + vendorHash, + ... + }@finalAttrs: + */ + finalAttrs: previousAttrs: + + let + phpDrv = finalAttrs.php or php; + composer = finalAttrs.composer or phpDrv.packages.composer; + composer-local-repo-plugin = callPackage ../../pkgs/composer-local-repo-plugin.nix { }; + in + assert (lib.assertMsg (previousAttrs ? src) "mkComposerRepository expects src argument."); + assert ( + lib.assertMsg (previousAttrs ? vendorHash) "mkComposerRepository expects vendorHash argument." + ); + assert (lib.assertMsg (previousAttrs ? version) "mkComposerRepository expects version argument."); + assert (lib.assertMsg (previousAttrs ? pname) "mkComposerRepository expects pname argument."); + assert ( + lib.assertMsg (previousAttrs ? composerNoDev) "mkComposerRepository expects composerNoDev argument." + ); + assert ( + lib.assertMsg ( + previousAttrs ? composerNoPlugins + ) "mkComposerRepository expects composerNoPlugins argument." + ); + assert ( + lib.assertMsg ( + previousAttrs ? composerNoScripts + ) "mkComposerRepository expects composerNoScripts argument." + ); + { + composerNoDev = previousAttrs.composerNoDev or true; + composerNoPlugins = previousAttrs.composerNoPlugins or true; + composerNoScripts = previousAttrs.composerNoScripts or true; + composerStrictValidation = previousAttrs.composerStrictValidation or true; + + name = "${previousAttrs.pname}-${previousAttrs.version}-composer-repository"; + + # See https://github.com/NixOS/nix/issues/6660 + dontPatchShebangs = previousAttrs.dontPatchShebangs or true; + + nativeBuildInputs = (previousAttrs.nativeBuildInputs or [ ]) ++ [ + composer + composer-local-repo-plugin + phpDrv + phpDrv.composerHooks.composerRepositoryHook + ]; + + buildInputs = previousAttrs.buildInputs or [ ]; + + strictDeps = previousAttrs.strictDeps or true; + + # Should we keep these empty phases? + configurePhase = + previousAttrs.configurePhase or '' + runHook preConfigure + + runHook postConfigure + ''; + + buildPhase = + previousAttrs.buildPhase or '' + runHook preBuild + + runHook postBuild + ''; + + doCheck = previousAttrs.doCheck or true; + checkPhase = + previousAttrs.checkPhase or '' + runHook preCheck + + runHook postCheck + ''; + + installPhase = + previousAttrs.installPhase or '' + runHook preInstall + + runHook postInstall + ''; + + doInstallCheck = previousAttrs.doInstallCheck or false; + installCheckPhase = + previousAttrs.installCheckPhase or '' + runHook preInstallCheck + + runHook postInstallCheck + ''; + + env = { + COMPOSER_CACHE_DIR = "/dev/null"; + COMPOSER_MIRROR_PATH_REPOS = "1"; + COMPOSER_HTACCESS_PROTECT = "0"; + COMPOSER_DISABLE_NETWORK = "0"; + }; + + outputHashMode = "recursive"; + outputHashAlgo = + if (finalAttrs ? vendorHash && finalAttrs.vendorHash != "") then null else "sha256"; + outputHash = finalAttrs.vendorHash or ""; + }; +in +args: (stdenvNoCC.mkDerivation args).overrideAttrs mkComposerRepositoryOverride diff --git a/pkgs/build-support/php/builders/v1/hooks/composer-install-hook.sh b/pkgs/build-support/php/builders/v1/hooks/composer-install-hook.sh new file mode 100644 index 0000000000000..a91263422bc84 --- /dev/null +++ b/pkgs/build-support/php/builders/v1/hooks/composer-install-hook.sh @@ -0,0 +1,126 @@ +declare composerRepository +declare version +declare composerNoDev +declare composerNoPlugins +declare composerNoScripts + +preConfigureHooks+=(composerInstallConfigureHook) +preBuildHooks+=(composerInstallBuildHook) +preCheckHooks+=(composerInstallCheckHook) +preInstallHooks+=(composerInstallInstallHook) + +source @phpScriptUtils@ + +composerInstallConfigureHook() { + echo "Executing composerInstallConfigureHook" + + if [[ ! -e "${composerRepository}" ]]; then + echo "No local composer repository found." + exit 1 + fi + + if [[ -e "$composerLock" ]]; then + cp "$composerLock" composer.lock + fi + + if [[ ! -f "composer.lock" ]]; then + setComposeRootVersion + + composer \ + --no-install \ + --no-interaction \ + --no-progress \ + ${composerNoDev:+--no-dev} \ + ${composerNoPlugins:+--no-plugins} \ + ${composerNoScripts:+--no-scripts} \ + update + + mkdir -p $out + cp composer.lock $out/ + + echo + echo -e "\e[31mERROR: No composer.lock found\e[0m" + echo + echo -e '\e[31mNo composer.lock file found, consider adding one to your repository to ensure reproducible builds.\e[0m' + echo -e "\e[31mIn the meantime, a composer.lock file has been generated for you in $out/composer.lock\e[0m" + echo + echo -e '\e[31mTo fix the issue:\e[0m' + echo -e "\e[31m1. Copy the composer.lock file from $out/composer.lock to the project's source:\e[0m" + echo -e "\e[31m cp $out/composer.lock <path>\e[0m" + echo -e '\e[31m2. Add the composerLock attribute, pointing to the copied composer.lock file:\e[0m' + echo -e '\e[31m composerLock = ./composer.lock;\e[0m' + echo + + exit 1 + fi + + echo "Validating consistency between composer.lock and ${composerRepository}/composer.lock" + if ! @cmp@ -s "composer.lock" "${composerRepository}/composer.lock"; then + echo + echo -e "\e[31mERROR: vendorHash is out of date\e[0m" + echo + echo -e "\e[31mcomposer.lock is not the same in $composerRepository\e[0m" + echo + echo -e "\e[31mTo fix the issue:\e[0m" + echo -e '\e[31m1. Set vendorHash to an empty string: `vendorHash = "";`\e[0m' + echo -e '\e[31m2. Build the derivation and wait for it to fail with a hash mismatch\e[0m' + echo -e '\e[31m3. Copy the "got: sha256-..." value back into the vendorHash field\e[0m' + echo -e '\e[31m You should have: vendorHash = "sha256-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=";\e[0m' + echo + + exit 1 + fi + + chmod +w composer.json composer.lock + + echo "Finished composerInstallConfigureHook" +} + +composerInstallBuildHook() { + echo "Executing composerInstallBuildHook" + + setComposeRootVersion + + # Since this file cannot be generated in the composer-repository-hook.sh + # because the file contains hardcoded nix store paths, we generate it here. + composer-local-repo-plugin --no-ansi build-local-repo-lock -m "${composerRepository}" . + + echo "Finished composerInstallBuildHook" +} + +composerInstallCheckHook() { + echo "Executing composerInstallCheckHook" + + checkComposerValidate + + echo "Finished composerInstallCheckHook" +} + +composerInstallInstallHook() { + echo "Executing composerInstallInstallHook" + + setComposeRootVersion + + # Finally, run `composer install` to install the dependencies and generate + # the autoloader. + composer \ + --no-interaction \ + --no-progress \ + ${composerNoDev:+--no-dev} \ + ${composerNoPlugins:+--no-plugins} \ + ${composerNoScripts:+--no-scripts} \ + install + + # Copy the relevant files only in the store. + mkdir -p "$out"/share/php/"${pname}" + cp -r . "$out"/share/php/"${pname}"/ + + # Create symlinks for the binaries. + jq -r -c 'try (.bin[] | select(test(".bat$")? | not) )' composer.json | while read -r bin; do + echo -e "\e[32mCreating symlink ${bin}...\e[0m" + mkdir -p "$out"/bin + ln -s "$out"/share/php/"${pname}"/"$bin" "$out"/bin/"$(basename "$bin")" + done + + echo "Finished composerInstallInstallHook" +} diff --git a/pkgs/build-support/php/builders/v1/hooks/composer-repository-hook.sh b/pkgs/build-support/php/builders/v1/hooks/composer-repository-hook.sh new file mode 100644 index 0000000000000..c4fa0d52126c1 --- /dev/null +++ b/pkgs/build-support/php/builders/v1/hooks/composer-repository-hook.sh @@ -0,0 +1,91 @@ +declare composerLock +declare version +declare composerNoDev +declare composerNoPlugins +declare composerNoScripts +declare composerStrictValidation + +preConfigureHooks+=(composerRepositoryConfigureHook) +preBuildHooks+=(composerRepositoryBuildHook) +preCheckHooks+=(composerRepositoryCheckHook) +preInstallHooks+=(composerRepositoryInstallHook) + +source @phpScriptUtils@ + +composerRepositoryConfigureHook() { + echo "Executing composerRepositoryConfigureHook" + + if [[ -e "$composerLock" ]]; then + cp $composerLock composer.lock + fi + + if [[ ! -f "composer.lock" ]]; then + setComposeRootVersion + + composer \ + --no-install \ + --no-interaction \ + --no-progress \ + ${composerNoDev:+--no-dev} \ + ${composerNoPlugins:+--no-plugins} \ + ${composerNoScripts:+--no-scripts} \ + update + + mkdir -p $out + cp composer.lock $out/ + + echo + echo -e "\e[31mERROR: No composer.lock found\e[0m" + echo + echo -e '\e[31mNo composer.lock file found, consider adding one to your repository to ensure reproducible builds.\e[0m' + echo -e "\e[31mIn the meantime, a composer.lock file has been generated for you in $out/composer.lock\e[0m" + echo + echo -e '\e[31mTo fix the issue:\e[0m' + echo -e "\e[31m1. Copy the composer.lock file from $out/composer.lock to the project's source:\e[0m" + echo -e "\e[31m cp $out/composer.lock <path>\e[0m" + echo -e '\e[31m2. Add the composerLock attribute, pointing to the copied composer.lock file:\e[0m' + echo -e '\e[31m composerLock = ./composer.lock;\e[0m' + echo + + exit 1 + fi + + echo "Finished composerRepositoryConfigureHook" +} + +composerRepositoryBuildHook() { + echo "Executing composerRepositoryBuildHook" + + mkdir -p repository + + setComposeRootVersion + + # Build the local composer repository + # The command 'build-local-repo' is provided by the Composer plugin + # nix-community/composer-local-repo-plugin. + composer-local-repo-plugin --no-ansi build-local-repo-lock ${composerNoDev:+--no-dev} -r repository + + echo "Finished composerRepositoryBuildHook" +} + +composerRepositoryCheckHook() { + echo "Executing composerRepositoryCheckHook" + + checkComposerValidate + + echo "Finished composerRepositoryCheckHook" +} + +composerRepositoryInstallHook() { + echo "Executing composerRepositoryInstallHook" + + mkdir -p $out + + cp -ar repository/. $out/ + + # Copy the composer.lock files to the output directory, to be able to validate consistency with + # the src composer.lock file where this fixed-output derivation is used + cp composer.lock $out/ + + echo "Finished composerRepositoryInstallHook" +} diff --git a/pkgs/build-support/php/builders/v1/hooks/default.nix b/pkgs/build-support/php/builders/v1/hooks/default.nix new file mode 100644 index 0000000000000..4c0ba1b18801c --- /dev/null +++ b/pkgs/build-support/php/builders/v1/hooks/default.nix @@ -0,0 +1,45 @@ +{ + lib, + makeSetupHook, + jq, + writeShellApplication, + moreutils, + cacert, + buildPackages, +}: + +let + php-script-utils = writeShellApplication { + name = "php-script-utils"; + runtimeInputs = [ jq ]; + text = builtins.readFile ./php-script-utils.bash; + }; +in +{ + composerRepositoryHook = makeSetupHook { + name = "composer-repository-hook.sh"; + propagatedBuildInputs = [ + jq + moreutils + cacert + ]; + substitutions = { + phpScriptUtils = lib.getExe php-script-utils; + }; + } ./composer-repository-hook.sh; + + composerInstallHook = makeSetupHook { + name = "composer-install-hook.sh"; + propagatedBuildInputs = [ + jq + moreutils + cacert + ]; + substitutions = { + # Specify the stdenv's `diff` by abspath to ensure that the user's build + # inputs do not cause us to find the wrong `diff`. + cmp = "${lib.getBin buildPackages.diffutils}/bin/cmp"; + phpScriptUtils = lib.getExe php-script-utils; + }; + } ./composer-install-hook.sh; +} diff --git a/pkgs/build-support/php/builders/v1/hooks/php-script-utils.bash b/pkgs/build-support/php/builders/v1/hooks/php-script-utils.bash new file mode 100644 index 0000000000000..bba0242e65d1e --- /dev/null +++ b/pkgs/build-support/php/builders/v1/hooks/php-script-utils.bash @@ -0,0 +1,77 @@ +declare version +declare composerStrictValidation + +setComposeRootVersion() { + set +e # Disable exit on error + + if [[ -v version ]]; then + echo -e "\e[32mSetting COMPOSER_ROOT_VERSION to $version\e[0m" + export COMPOSER_ROOT_VERSION=$version + fi + + set -e +} + +checkComposerValidate() { + if ! composer validate --strict --no-ansi --no-interaction --quiet --no-check-all --no-check-lock; then + if [ "1" == "${composerStrictValidation-}" ]; then + echo + echo -e "\e[31mERROR: composer files validation failed\e[0m" + echo + echo -e '\e[31mThe validation of the composer.json failed.\e[0m' + echo -e '\e[31mMake sure that the file composer.json is valid.\e[0m' + echo + echo -e '\e[31mTo address the issue efficiently, follow one of these steps:\e[0m' + echo -e '\e[31m 1. File an issue in the project'\''s issue tracker with detailed information, and apply any available remote patches as a temporary solution '\('with fetchpatch'\)'.\e[0m' + echo -e '\e[31m 2. If an immediate fix is needed or if reporting upstream isn'\''t suitable, develop a temporary local patch.\e[0m' + echo + exit 1 + else + echo + echo -e "\e[33mWARNING: composer files validation failed\e[0m" + echo + echo -e '\e[33mThe validation of the composer.json failed.\e[0m' + echo -e '\e[33mMake sure that the file composer.json is valid.\e[0m' + echo + echo -e '\e[33mTo address the issue efficiently, follow one of these steps:\e[0m' + echo -e '\e[33m 1. File an issue in the project'\''s issue tracker with detailed information, and apply any available remote patches as a temporary solution with '\('with fetchpatch'\)'.\e[0m' + echo -e '\e[33m 2. If an immediate fix is needed or if reporting upstream isn'\''t suitable, develop a temporary local patch.\e[0m' + echo + echo -e '\e[33mThis check is not blocking, but it is recommended to fix the issue.\e[0m' + echo + fi + fi + + if ! composer validate --strict --no-ansi --no-interaction --quiet --no-check-all --check-lock; then + if [ "1" == "${composerStrictValidation-}" ]; then + echo + echo -e "\e[31mERROR: composer files validation failed\e[0m" + echo + echo -e '\e[31mThe validation of the composer.json and composer.lock failed.\e[0m' + echo -e '\e[31mMake sure that the file composer.lock is consistent with composer.json.\e[0m' + echo + echo -e '\e[31mThis often indicates an issue with the upstream project, which can typically be resolved by reporting the issue to the relevant project maintainers.\e[0m' + echo + echo -e '\e[31mTo address the issue efficiently, follow one of these steps:\e[0m' + echo -e '\e[31m 1. File an issue in the project'\''s issue tracker with detailed information '\('run '\''composer update --lock --no-install'\'' to fix the issue'\)', and apply any available remote patches as a temporary solution with '\('with fetchpatch'\)'.\e[0m' + echo -e '\e[31m 2. If an immediate fix is needed or if reporting upstream isn'\''t suitable, develop a temporary local patch.\e[0m' + echo + exit 1 + else + echo + echo -e "\e[33mWARNING: composer files validation failed\e[0m" + echo + echo -e '\e[33mThe validation of the composer.json and composer.lock failed.\e[0m' + echo -e '\e[33mMake sure that the file composer.lock is consistent with composer.json.\e[0m' + echo + echo -e '\e[33mThis often indicates an issue with the upstream project, which can typically be resolved by reporting the issue to the relevant project maintainers.\e[0m' + echo + echo -e '\e[33mTo address the issue efficiently, follow one of these steps:\e[0m' + echo -e '\e[33m 1. File an issue in the project'\''s issue tracker with detailed information '\('run '\''composer update --lock --no-install'\'' to fix the issue'\)', and apply any available remote patches as a temporary solution with '\('with fetchpatch'\)'.\e[0m' + echo -e '\e[33m 2. If an immediate fix is needed or if reporting upstream isn'\''t suitable, develop a temporary local patch.\e[0m' + echo + echo -e '\e[33mThis check is not blocking, but it is recommended to fix the issue.\e[0m' + echo + fi + fi +} |