PHP extensions not building against PHP with overridden attributes

I’m trying to get a derivation of PHP that has debug enabled via a configuration flag, as well as an extension built against the debug enabled PHP, however I can’t figure out the overlay incantation required.

shell.nix

{ pkgs ? import <nixpkgs> {
    overlays = [ (import ./overlays.nix) ];
  }
}: pkgs.mkShell rec {
  name = "php-debug";
  buildInputs = [
    pkgs.php
  ];
}

overlays.nix

self: super:
let
  # works if used directly, php -v reports DEBUG enabled
  # but only the built-in extensions are built against it
  php_with_debug = super.php.unwrapped.overrideAttrs (super: {
    configureFlags = super.configureFlags ++ ["--enable-debug"];
  });

  # no affect it seems, or at least not what I'm expecting
  php_wrapped = super.php.overrideAttrs (super: {
    unwrapped = php_with_debug;
  });

  # no affect it seems, or at least not what I'm expecting
  php_po = php_wrapped.override {
    packageOverrides = final: prev: {
      phpPackage = php_wrapped;
    };
  };

  # attempting to enable the extension
  php_we = php_po.withExtensions ({ enabled, all }: with all; [
    pcov
  ]);

  # unsurprisingly, smashing them together doesn't work
  php = php_we.overrideDerivation (super: {
    unwrapped = php_with_debug;
    paths = [php_with_debug];
  });
in {
  inherit php;
}

test command

nix-shell --command 'php -m; php -v'

Bad output contains:

PHP Warning:  PHP Startup: pcov: Unable to initialize module
Module compiled with build ID=API20190902,NTS
PHP    compiled with build ID=API20190902,NTS,debug

Expected good output would look something like:

[PHP Modules]
Core
date
hash
libxml
pcov
pcre
Phar
Reflection
SPL
standard
xml

[Zend Modules]

PHP 7.4.15 (cli) (built: Feb  9 2021 03:03:58) ( NTS DEBUG )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
❯ sw_vers
ProductName:    macOS
ProductVersion: 11.1
BuildVersion:   20C69

❯ nix-instantiate --eval -E '(import <nixpkgs> {}).lib.version'
"21.03pre268731.078f8937952"

There are actually two issues with your code:

  • pcov module is built using buildPecl which uses php attribute from the scope. You should therefore override php, not phpPackage (it was actually changed recently).

  • unwrapped is defined in a passthru so you will have to override it as such or it will be shadowed.

    php_wrapped = pkgs.php.overrideAttrs (attrs: {
     passthru = attrs.passthru // {
       unwrapped = php_with_debug;
     };
    });
    

    Though I would not recommend it since it might no longer reflect reality after future overlays.

    Again, looking at the source code, it does not look like there is a convenient way to override it since the php value is created using generic function taken from the parent scope, not any function arguments.

    I would suggest just calling withExtensions on the unwrapped PHP but that would lose the list of extensions enabled by default. And also that does not actually work since mkBuildEnv will refer to the pre-overridden php. So it looks like it is not possible to do it cleanly with the PHP package as currently written.

1 Like

The following hack seems to work:

{ pkgs ? import <nixpkgs> { }
}:

let
  php_original = pkgs.php;

  # Unwrapped PHP built in debug mode.
  php_with_debug = php_original.unwrapped.overrideAttrs (attrs: {
    configureFlags = attrs.configureFlags ++ ["--enable-debug"];
  });

  # Wrapped PHP with extensions built against `php_with_debug`
  php_po = php_original.override {
    packageOverrides = final: prev: {
      php = prev.php.overrideAttrs (attrs: {
        passthru = attrs.passthru // {
          # `buildPecl` finds PHP for building modules this way.
          unwrapped = php_with_debug;
        };
      });
    };
  };

  # Wrapped PHP with extra extensions built against `php_with_debug`.
  # Unfortunately, each `mkBuildEnv`/`.withExtensions` call creates
  # a wrapped PHP with the `unwrapped` attribute pointing back
  # to the original unwrapped PHP, not our debug mode one.
  # (Though the extensions will keep being linked against
  # the correct one from `packageOverrides`.)
  php_we = php_po.withExtensions ({ enabled, all }: with all; [
    pcov
  ]);

  # Let’s just replace the incorrect PHP at the end,
  # to finish this chain of ugly hacks.
  php = pkgs.replaceDependency {
    drv = php_we;
    oldDependency = php_we.unwrapped;
    newDependency = php_with_debug;
  };
in
  pkgs.mkShell rec {
    name = "php-debug";
    buildInputs = [
      php
    ];
  }
[PHP Modules]
Core
date
hash
libxml
pcov
pcre
Phar
Reflection
SPL
standard
xml

[Zend Modules]

PHP 7.4.14 (cli) (built: Jan  5 2021 10:45:18) ( ZTS DEBUG )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
1 Like

Awesome. First time I’ve seen replaceDependency. Exposes yet again my biggest hurdle with Nix is that while there’s pretty much always some way, finding the right mix of custom logic plus functions from nixpkgs is rough. That and tracing variable lifetime through a stack of function calls. Getting easier with practice at least.

Thankfully, I don’t expect to run this stack of ugly hacks for long, but it works around a different problem (pcov on php7.4 without debug on MacOS crashes) well enough for now.

Really appreciate the reply/solution and most importantly, describing how it all works, so I’ve got a much better chance at solving this sort of problem in the future on my own.