Skip to content

PHP: add new builder for PHP projects #225401

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

Closed
wants to merge 34 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
738a683
composer: init at 2.5.8
drupol Jun 12, 2023
801067d
composer-local-repo-plugin: init at ff33aaf5454888e2669f366639be9877d…
drupol Jun 12, 2023
aad1e20
php: add new builder `buildComposerProject`
drupol Apr 9, 2023
5e50bea
phpPackages.composer: use `buildComposerProject` builder
drupol Apr 14, 2023
f252bac
drush-launcher: init at 0.10.2
drupol Apr 15, 2023
ae737e2
n98-magerun: use `builbComposerProject` builder
drupol Apr 11, 2023
2d23d02
n98-magerun2: use `builbComposerProject` builder
drupol Apr 11, 2023
9b24a33
phpactor: use `buildComposerProject` builder
drupol Apr 11, 2023
d2dab07
phpbench: init at 1.2.10
drupol May 31, 2023
a663b68
phpDocumentor: init at 3.3.1
drupol Jun 2, 2023
096c1aa
pdepend: use `builbComposerProject` builder
drupol Apr 11, 2023
e6df051
phpunit: use `buildComposerProject` builder
drupol Apr 11, 2023
7ae4d8c
platformsh: use `buildComposerProject` builder
drupol Apr 15, 2023
9d08205
robo: init at 4.0.4
drupol Apr 15, 2023
cfd3894
wp-cli: use `buildComposerProject` builder
drupol Apr 15, 2023
023afcc
phpPackages.box: use `buildComposerProject` builder
drupol Apr 9, 2023
3e9ada5
phpPackages.deployer: use `buildComposerProject` builder
drupol Apr 15, 2023
b22cdf9
phpPackages.grumphp: use `buildComposerProject` builder
drupol Apr 9, 2023
0229594
phpPackages.phan: use `builbComposerProject` builder
drupol Apr 11, 2023
71dca4e
phpPackages.phing: use `builbComposerProject` builder
drupol Apr 11, 2023
7efa1ac
phpPackages.phive: use `builbComposerProject` builder
drupol Apr 11, 2023
3c12b2f
phpPackages.php-cs-fixer: use `buildComposerProject` builder
drupol Apr 9, 2023
d442679
phpPackages.php-parallel-lint: use `builbComposerProject` builder
drupol Apr 11, 2023
5170504
phpPackages.phpcbf: removed, it is now included in phpcs
drupol Apr 11, 2023
93e91d5
phpPackages.phpcs: use `builbComposerProject` builder
drupol Apr 11, 2023
8c23df9
phpPackages.phpmd: use `builbComposerProject` builder
drupol Apr 11, 2023
9a5a1a2
phpPackages.phpstan: use `buildComposerProject` builder
drupol Apr 11, 2023
270bd3a
phpPackages.psalm: use `buildComposerProject` builder
drupol May 24, 2023
5d6d60f
phpPackages.psysh: use `buildComposerProject` builder
drupol Apr 9, 2023
22cb649
drush: removed, standalone installation are no more supported
drupol Apr 15, 2023
5221747
php: update documentation
drupol Apr 21, 2023
8b37514
composer-local-repo-plugin: Stop exposing this internal tool
etu Aug 9, 2023
affe641
composer: Stop exposing composer built from a phar file
etu Aug 9, 2023
da64d1d
php: Fix shellcheck string warnings in composer-install-hook
etu Aug 9, 2023
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
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ 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 @aanderse @drupol @etu @globin @ma27 @talyz
/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
Expand Down
140 changes: 139 additions & 1 deletion doc/languages-frameworks/php.section.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ package: a project may depend on certain extensions and `composer`
won't work with that project unless those extensions are loaded.

Example of building `composer` with additional extensions:

```nix
(php.withExtensions ({ all, enabled }:
enabled ++ (with all; [ imagick redis ]))
Expand All @@ -138,7 +139,9 @@ Example of building `composer` with additional extensions:

### Overriding PHP packages {#ssec-php-user-guide-overriding-packages}

`php-packages.nix` form a scope, allowing us to override the packages defined within. For example, to apply a patch to a `mysqlnd` extension, you can simply pass an overlay-style function to `php`’s `packageOverrides` argument:
`php-packages.nix` form a scope, allowing us to override the packages defined
within. For example, to apply a patch to a `mysqlnd` extension, you can simply
pass an overlay-style function to `php`’s `packageOverrides` argument:

```nix
php.override {
Expand All @@ -153,3 +156,138 @@ php.override {
};
}
```

### Building PHP projects {#ssec-building-php-projects}

With [Composer](https://getcomposer.org/), you can effectively build PHP
projects by streamlining dependency management. As the de-facto standard
dependency manager for PHP, Composer enables you to declare and manage the
libraries your project relies on, ensuring a more organized and efficient
development process.

Composer is not a package manager in the same sense as `Yum` or `Apt` are. Yes,
it deals with "packages" or libraries, but it manages them on a per-project
basis, installing them in a directory (e.g. `vendor`) inside your project. By
default, it does not install anything globally. This idea is not new and
Composer is strongly inspired by node's `npm` and ruby's `bundler`.

Currently, there is no other PHP tool that offers the same functionality as
Composer. Consequently, incorporating a helper in Nix to facilitate building
such applications is a logical choice.

In a Composer project, dependencies are defined in a `composer.json` file,
while their specific versions are locked in a `composer.lock` file. Some
Composer-based projects opt to include this `composer.lock` file in their source
code, while others choose not to.

In Nix, there are multiple approaches to building a Composer-based project.

One such method is the `php.buildComposerProject` helper function, which serves
as a wrapper around `mkDerivation`.

Using this function, you can build a PHP project that includes both a
`composer.json` and `composer.lock` file. If the project specifies binaries
using the `bin` attribute in `composer.json`, these binaries will be
automatically linked and made accessible in the derivation. In this context,
"binaries" refer to PHP scripts that are intended to be executable.

To use the helper effectively, simply add the `vendorHash` attribute, which
enables the wrapper to handle the heavy lifting.

Internally, the helper operates in three stages:

1. It constructs a `composerRepository` attribute derivation by creating a
composer repository on the filesystem containing dependencies specified in
`composer.json`. This process uses the function
`php.mkComposerRepository` which in turn uses the
`php.composerHooks.composerRepositoryHook` hook. Internaly this function uses
a custom
[Composer plugin](https://github.com/drupol/composer-local-repo-plugin) to
generate the repository.
2. The resulting `composerRepository` derivation is then used by the
`php.composerHooks.composerInstallHook` hook, which is responsible for
creating the final `vendor` directory.
3. Any "binary" specified in the `composer.json` are linked and made accessible
in the derivation.

As the autoloader optimization can be activated directly within the
`composer.json` file, we do not enable any autoloader optimization flags.

To customize the PHP version, you can specify the `php` attribute. Similarly, if
you wish to modify the Composer version, use the `composer` attribute. It is
important to note that both attributes should be of the `derivation` type.

Here's an example of working code example using `php.buildComposerProject`:

```nix
{ php, fetchFromGitHub }:

php.buildComposerProject (finalAttrs: {
pname = "php-app";
version = "1.0.0";

src = fetchFromGitHub {
owner = "git-owner";
repo = "git-repo";
rev = finalAttrs.version;
hash = "sha256-VcQRSss2dssfkJ+iUb5qT+FJ10GHiFDzySigcmuVI+8=";
};

# PHP version containing the `ast` extension enabled
php = php.buildEnv {
extensions = ({ enabled, all }: enabled ++ (with all; [
ast
]));
};

# The composer vendor hash
vendorHash = "sha256-86s/F+/5cBAwBqZ2yaGRM5rTGLmou5//aLRK5SA0WiQ=";

# If the composer.lock file is missing from the repository, add it:
# composerLock = ./path/to/composer.lock;
})
```

In case the file `composer.lock` is missing from the repository, it is possible
to specify it using the `composerLock` attribute.

The other method is to use all these methods and hooks individually. This has
the advantage of building a PHP library within another derivation very easily
when necessary.

Here's a working code example to build a PHP library using `mkDerivation` and
separate functions and hooks:

```nix
{ stdenvNoCC, fetchFromGitHub, php }:

stdenvNoCC.mkDerivation (finalAttrs:
let
src = fetchFromGitHub {
owner = "git-owner";
repo = "git-repo";
rev = finalAttrs.version;
hash = "sha256-VcQRSss2dssfkJ+iUb5qT+FJ10GHiFDzySigcmuVI+8=";
};
in {
inherit src;
pname = "php-app";
version = "1.0.0";

buildInputs = [ php ];

nativeBuildInputs = [
php.packages.composer
# This hook will use the attribute `composerRepository`
php.composerHooks.composerInstallHook
];

composerRepository = php.mkComposerRepository {
inherit (finalAttrs) src;
# Specifying a custom composer.lock since it is not present in the sources.
composerLock = ./composer.lock;
# The composer vendor hash
vendorHash = "sha256-86s/F+/5cBAwBqZ2yaGRM5rTGLmou5//aLRK5SA0WiQ=";
};
})
```
62 changes: 62 additions & 0 deletions pkgs/build-support/php/build-composer-project.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{ callPackage, stdenvNoCC, lib, writeTextDir, php, makeBinaryWrapper, fetchFromGitHub, fetchurl }:

let
buildComposerProjectOverride = finalAttrs: previousAttrs:

let
phpDrv = finalAttrs.php or php;
composer = finalAttrs.composer or phpDrv.packages.composer;
composer-local-repo-plugin = callPackage ./composer-local-repo-plugin.nix { };
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
80 changes: 80 additions & 0 deletions pkgs/build-support/php/build-composer-repository.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
{ callPackage, stdenvNoCC, lib, writeTextDir, fetchFromGitHub, 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 ./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.");
{
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
Loading