about summary refs log tree commit diff
path: root/pkgs/build-support/php/builders
diff options
context:
space:
mode:
Diffstat (limited to 'pkgs/build-support/php/builders')
-rw-r--r--pkgs/build-support/php/builders/default.nix8
-rw-r--r--pkgs/build-support/php/builders/v1/build-composer-project.nix103
-rw-r--r--pkgs/build-support/php/builders/v1/build-composer-repository.nix121
-rw-r--r--pkgs/build-support/php/builders/v1/hooks/composer-install-hook.sh126
-rw-r--r--pkgs/build-support/php/builders/v1/hooks/composer-repository-hook.sh91
-rw-r--r--pkgs/build-support/php/builders/v1/hooks/default.nix45
-rw-r--r--pkgs/build-support/php/builders/v1/hooks/php-script-utils.bash77
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
+}