From b36ad2f51797d82ddd4479835c0edd71b3386865 Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Sun, 9 Apr 2023 11:53:42 +0200 Subject: [PATCH] php: add new builder `buildComposerProject` --- .github/CODEOWNERS | 3 +- .../php/build-composer-project.nix | 61 ++++++++++ .../php/build-composer-repository.nix | 79 +++++++++++++ pkgs/build-support/{ => php}/build-pecl.nix | 0 .../php/hooks/composer-install-hook.sh | 109 ++++++++++++++++++ .../php/hooks/composer-repository-hook.sh | 66 +++++++++++ pkgs/build-support/php/hooks/default.nix | 21 ++++ pkgs/development/interpreters/php/generic.nix | 2 +- pkgs/top-level/php-packages.nix | 11 +- 9 files changed, 347 insertions(+), 5 deletions(-) create mode 100644 pkgs/build-support/php/build-composer-project.nix create mode 100644 pkgs/build-support/php/build-composer-repository.nix rename pkgs/build-support/{ => php}/build-pecl.nix (100%) create mode 100644 pkgs/build-support/php/hooks/composer-install-hook.sh create mode 100644 pkgs/build-support/php/hooks/composer-repository-hook.sh create mode 100644 pkgs/build-support/php/hooks/default.nix diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 8bea23cd6f8e..1c183c93e834 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -257,7 +257,8 @@ pkgs/development/python-modules/buildcatrust/ @ajs124 @lukegb @mweinelt # PHP interpreter, packages, extensions, tests and documentation /doc/languages-frameworks/php.section.md @aanderse @drupol @etu @globin @ma27 @talyz /nixos/tests/php @aanderse @drupol @etu @globin @ma27 @talyz -/pkgs/build-support/build-pecl.nix @aanderse @drupol @etu @globin @ma27 @talyz +/pkgs/build-support/php/build-pecl.nix @aanderse @drupol @etu @globin @ma27 @talyz +/pkgs/build-support/php @drupol @etu /pkgs/development/interpreters/php @jtojnar @aanderse @drupol @etu @globin @ma27 @talyz /pkgs/development/php-packages @aanderse @drupol @etu @globin @ma27 @talyz /pkgs/top-level/php-packages.nix @jtojnar @aanderse @drupol @etu @globin @ma27 @talyz diff --git a/pkgs/build-support/php/build-composer-project.nix b/pkgs/build-support/php/build-composer-project.nix new file mode 100644 index 000000000000..f9589ace1309 --- /dev/null +++ b/pkgs/build-support/php/build-composer-project.nix @@ -0,0 +1,61 @@ +{ stdenvNoCC, lib, writeTextDir, php, makeBinaryWrapper, fetchFromGitHub, fetchurl, composer-local-repo-plugin }: + +let + buildComposerProjectOverride = finalAttrs: previousAttrs: + + let + phpDrv = finalAttrs.php or php; + composer = finalAttrs.composer or phpDrv.packages.composer; + composerLock = finalAttrs.composerLock or null; + in + { + nativeBuildInputs = (previousAttrs.nativeBuildInputs or [ ]) ++ [ + composer + composer-local-repo-plugin + 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 + ''; + + composerRepository = phpDrv.mkComposerRepository { + inherit composer composer-local-repo-plugin composerLock; + inherit (finalAttrs) patches pname src vendorHash version; + }; + + meta = previousAttrs.meta or { } // { + platforms = lib.platforms.all; + }; + }; +in +args: (stdenvNoCC.mkDerivation args).overrideAttrs buildComposerProjectOverride diff --git a/pkgs/build-support/php/build-composer-repository.nix b/pkgs/build-support/php/build-composer-repository.nix new file mode 100644 index 000000000000..7a7ba6f146c0 --- /dev/null +++ b/pkgs/build-support/php/build-composer-repository.nix @@ -0,0 +1,79 @@ +{ stdenvNoCC, lib, writeTextDir, fetchFromGitHub, php, composer-local-repo-plugin }: + +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; + 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."); + { + 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.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 + ''; + + 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/build-pecl.nix b/pkgs/build-support/php/build-pecl.nix similarity index 100% rename from pkgs/build-support/build-pecl.nix rename to pkgs/build-support/php/build-pecl.nix diff --git a/pkgs/build-support/php/hooks/composer-install-hook.sh b/pkgs/build-support/php/hooks/composer-install-hook.sh new file mode 100644 index 000000000000..139f6357c5d7 --- /dev/null +++ b/pkgs/build-support/php/hooks/composer-install-hook.sh @@ -0,0 +1,109 @@ +declare composerHomeDir +declare composerRepository +declare version + +preConfigureHooks+=(composerInstallConfigureHook) +preBuildHooks+=(composerInstallBuildHook) +preCheckHooks+=(composerInstallCheckHook) +preInstallHooks+=(composerInstallInstallHook) + +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 + echo "No composer.lock file found, consider adding one to your repository to ensure reproducible builds." + + if [[ -f "${composerRepository}/composer.lock" ]]; then + cp ${composerRepository}/composer.lock composer.lock + fi + + echo "Using an autogenerated composer.lock file." + fi + + chmod +w composer.json composer.lock + + echo "Finished composerInstallConfigureHook" +} + +composerInstallBuildHook() { + echo "Executing composerInstallBuildHook" + + # 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 -p ${composerRepository} > packages.json + + # Remove all the repositories of type "composer" + # from the composer.json file. + jq -r -c 'del(try .repositories[] | select(.type == "composer"))' composer.json | sponge composer.json + + # Configure composer to disable packagist and avoid using the network. + composer config repo.packagist false + # Configure composer to use the local repository. + composer config repo.composer composer file://$PWD/packages.json + + # Since the composer.json file has been modified in the previous step, the + # composer.lock file needs to be updated. + COMPOSER_DISABLE_NETWORK=1 \ + COMPOSER_ROOT_VERSION="${version}" \ + composer \ + --lock \ + --no-ansi \ + --no-install \ + --no-interaction \ + --no-plugins \ + --no-scripts \ + update + + echo "Finished composerInstallBuildHook" +} + +composerInstallCheckHook() { + echo "Executing composerInstallCheckHook" + + composer validate --no-ansi --no-interaction + + echo "Finished composerInstallCheckHook" +} + +composerInstallInstallHook() { + echo "Executing composerInstallInstallHook" + + # Finally, run `composer install` to install the dependencies and generate + # the autoloader. + # The COMPOSER_ROOT_VERSION environment variable is needed only for + # vimeo/psalm. + COMPOSER_CACHE_DIR=/dev/null \ + COMPOSER_DISABLE_NETWORK=1 \ + COMPOSER_ROOT_VERSION="${version}" \ + COMPOSER_MIRROR_PATH_REPOS="1" \ + composer \ + --no-ansi \ + --no-interaction \ + --no-scripts \ + --no-plugins \ + install + + # Remove packages.json, we don't need it in the store. + rm packages.json + + # 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[]' composer.json | while read bin; do + mkdir -p $out/share/php/${pname} $out/bin + ln -s $out/share/php/${pname}/$bin $out/bin/$(basename $bin) + done + + echo "Finished composerInstallInstallHook" +} diff --git a/pkgs/build-support/php/hooks/composer-repository-hook.sh b/pkgs/build-support/php/hooks/composer-repository-hook.sh new file mode 100644 index 000000000000..707c94452256 --- /dev/null +++ b/pkgs/build-support/php/hooks/composer-repository-hook.sh @@ -0,0 +1,66 @@ +declare composerHomeDir +declare composerLock +declare version + +preConfigureHooks+=(composerRepositoryConfigureHook) +preBuildHooks+=(composerRepositoryBuildHook) +preCheckHooks+=(composerRepositoryCheckHook) +preInstallHooks+=(composerRepositoryInstallHook) + +composerRepositoryConfigureHook() { + echo "Executing composerRepositoryConfigureHook" + + if [[ -e "$composerLock" ]]; then + cp $composerLock composer.lock + fi + + if [[ ! -f "composer.lock" ]]; then + echo "No composer.lock file found, consider adding one to your repository to ensure reproducible builds." + composer \ + --no-ansi \ + --no-install \ + --no-interaction \ + --no-plugins \ + --no-scripts \ + update + echo "Using an autogenerated composer.lock file." + fi + + echo "Finished composerRepositoryConfigureHook" +} + +composerRepositoryBuildHook() { + echo "Executing composerRepositoryBuildHook" + + mkdir -p repository + + # Build the local composer repository + # The command 'build-local-repo' is provided by the Composer plugin + # nix-community/composer-local-repo-plugin. + COMPOSER_CACHE_DIR=/dev/null \ + composer-local-repo-plugin --no-ansi build-local-repo -r repository + + echo "Finished composerRepositoryBuildHook" +} + +composerRepositoryCheckHook() { + echo "Executing composerRepositoryCheckHook" + + composer validate --no-ansi --no-interaction + + echo "Finished composerRepositoryCheckHook" +} + +composerRepositoryInstallHook() { + echo "Executing composerRepositoryInstallHook" + + mkdir -p $out + + cp -ar repository/. $out/ + + # Copy the composer.lock files to the output directory, in case it has been + # autogenerated. + cp composer.lock $out/ + + echo "Finished composerRepositoryInstallHook" +} diff --git a/pkgs/build-support/php/hooks/default.nix b/pkgs/build-support/php/hooks/default.nix new file mode 100644 index 000000000000..98198f012879 --- /dev/null +++ b/pkgs/build-support/php/hooks/default.nix @@ -0,0 +1,21 @@ +{ makeSetupHook +, php +, jq +, moreutils +}: + +{ + composerRepositoryHook = makeSetupHook + { + name = "composer-repository-hook.sh"; + propagatedBuildInputs = [ php jq moreutils ]; + substitutions = { }; + } ./composer-repository-hook.sh; + + composerInstallHook = makeSetupHook + { + name = "composer-install-hook.sh"; + propagatedBuildInputs = [ php jq moreutils ]; + substitutions = { }; + } ./composer-install-hook.sh; +} diff --git a/pkgs/development/interpreters/php/generic.nix b/pkgs/development/interpreters/php/generic.nix index cee1e833f9a6..38ac50081423 100644 --- a/pkgs/development/interpreters/php/generic.nix +++ b/pkgs/development/interpreters/php/generic.nix @@ -159,7 +159,7 @@ let nixos = lib.recurseIntoAttrs nixosTests."php${lib.strings.replaceStrings [ "." ] [ "" ] (lib.versions.majorMinor php.version)}"; package = tests.php; }; - inherit (php-packages) extensions buildPecl mkExtension; + inherit (php-packages) extensions buildPecl mkComposerRepository buildComposerProject composerHooks mkExtension; packages = php-packages.tools; meta = php.meta // { outputsToInstall = [ "out" ]; diff --git a/pkgs/top-level/php-packages.nix b/pkgs/top-level/php-packages.nix index 7430c98586b7..aad260fc3c00 100644 --- a/pkgs/top-level/php-packages.nix +++ b/pkgs/top-level/php-packages.nix @@ -1,4 +1,6 @@ { stdenv +, config +, callPackages , lib , pkgs , phpPackage @@ -44,12 +46,15 @@ }: lib.makeScope pkgs.newScope (self: with self; { - buildPecl = import ../build-support/build-pecl.nix { + buildPecl = callPackage ../build-support/php/build-pecl.nix { php = php.unwrapped; - inherit lib; - inherit (pkgs) stdenv autoreconfHook fetchurl re2c nix-update-script; }; + composerHooks = callPackages ../build-support/php/hooks { }; + + mkComposerRepository = callPackage ../build-support/php/build-composer-repository.nix { }; + buildComposerProject = callPackage ../build-support/php/build-composer-project.nix { }; + # Wrap mkDerivation to prepend pname with "php-" to make names consistent # with how buildPecl does it and make the file easier to overview. mkDerivation = origArgs: