Unable to build package in "nix-shell" or "nix develop" but nix-build or nix build . works fine

I am trying to build a C++ package using Nix however I am getting different results when I am in a nix-shell (via either nix-shell or nix develop) vs. when I just run nix-build or nix build .

I apologize however the package is proprietary so I cannot share too many internal details, but hopefully the following will be enough to get the discussion going.

I have a default.nix and flake.nix (I had wondered whether there would be any difference between nix-shell and nix develop for example, however the end result is the same).

default.nix:

{ pkgs ? import <nixpkgs> {}; }:

with pkgs;

gcc10Stdenv.mkDerivation {
  name = "<redacted>";
  src = ./.;

  nativeBuildInputs = [ cmake ninja ];
  buildInputs = with xorg; [ libX11 libXext libXi libXtst openssl ];

  configurePhase = ''
    cmake -S . -B build <redacted>...
  '';
  buildPhase = ''
    cmake --build build --target all --parallel 16
  '';
  installPhase = ''
    mkdir -p $out/bin
    cp build/<redacted> $out/bin
    ...
  '';
}

flake.nix:

{
  inputs.nixpkgs.url = github:nixos/nixpkgs/nixpkgs-unstable;

  outputs = { self, nixpkgs }:
  let
    pkgs = nixpkgs.legacyPackages.x86_64-linux;
  in
  {
    defaultPackage.x86_64-linux = pkgs.callPackage ./default.nix { };
  }
}

So. When I do either nix-build or nix build . everything works fine. The package builds, the executables end up in ./result/bin and I can run them just as normally would (i.e. when building the same artifacts using manually installed dependencies, e.g. through yum and the like).

However, when I run nix-shell and try to run the configure and build commands directly;

$ nix-shell

[nix-shell:/path/to/workspace]$ cmake -S . -B build -G Ninja <redacted>...
...

[nix-shell:/path/to/workspace]$ cmake --build build --target all --parallel 16

I get errors from within glibc and the std library. Examples:

/path/to/glibc-2.33-50-dev/include/stdlib.h:935:5: error: expected initializer before '__attr_access'
/path/to/gcc-10.3.0/include/c++/10.3.0/mutex:336:17: error: 'pthread_mutex_clocklock' was not declared in this scope; did you mean 'pthread_mutex_unlock'?
/path/to/gcc-10.3.0/include/c++/10.3.0/condition_variable:209:2: error: there are no arguments to 'pthread_cond_clockwait' that depend on a template parameter, so a declaration of 'pthread_cond_clockwait' must be available [-fpermissive]

The __attr_access error is quite mysterious to me and I have no leads on that front. For the second error above it looks to be gated by some internal macros e.g. _GLIBCXX_USE_PTHREAD_MUTEX_CLOCKLOCK which is defined in “bits/c++config.h”, so no problems there. I hit a dead end when I look for the declaration of pthread_mutex_clocklock which is gated by a _GNU_SOURCE macro for which I can not find a definition, which corroborates the error.

So my question is: why the difference between nix-build and nix-shell? The documentation for the latter even explicitly says that you can then invoke the configure and build commands as you normally would. So I am confused about what might be going wrong here. Any help in identifying potential problems or suggestions for further debugging would be appreciated!

Note that, in nix develop, “configurePhase” and “buildPhase” do not appear to be configured properly. Perhaps I am misreading the documentation on the usage of nix develop, however;

$ configurePhase
...
no configure script, doing nothing

$ buildPhase
no Makefile, doing nothing

I have similar problems with packages in nixpkgs, too.
Example is networkmanager. I can nix-build it but when I nix-shell or nix develop it and go through unpackPhase->patchPhase->configurePhase I cannot pass the configure one.
Manually run configurePhase seems to be executing the ./configure script while the nix-build one is picking up the mesonConfigurePhase. No idea how to approach that.

I know this is old, but I ran into this same issue with a Meson-based package.

tl;dr you can normally replicate the canonical build process by invoking the genericBuild function within the shell rather than the individual phases. The Nixpkgs Reference Manual contains some more advanced examples of controlling the phases when invoking genericBuild.

The problem is that nix doesn’t call the phases directly, it merely runs the command specified by the derivation’s builder and args attributes. For stdenv-based derivations, these always refer to bash and generic-builder.sh, respectively. You can verify this on a flakes-based system for the hello package as follows:

$ nix eval nixpkgs#hello.builder
/nix/store/5jw69mbaj5dg4l2bj58acg3gxywfszpj-bash-5.2p26/bin/bash

$ nix eval nixpkgs#hello.args
[ "-e" /nix/store/2nnisw4pxbifisbkg58hrnis9ycs5ah1-source/pkgs/stdenv/generic/default-builder.sh ]

Now if we check the contents of the default-builder.sh file output above, we can see that it calls genericBuild.

$ cat /nix/store/2nnisw4pxbifisbkg58hrnis9ycs5ah1-source/pkgs/stdenv/generic/default-builder.sh
if [ -e "$NIX_ATTRS_SH_FILE" ]; then . "$NIX_ATTRS_SH_FILE"; elif [ -f .attrs.sh ]; then . .attrs.sh; fi

source $stdenv/setup
genericBuild

The genericBuild function will use alternate *Phase functions if their names are set via the *Phase variables (you can have both simultaneously in bash, lol). And you can see that the setup hooks for both Ninja and Meson (also here) set these, which is why the default configurePhase etc. do not work with these build systems.