Python c API, error ld: undefined reference

I’m trying to compile c code using the python API.

main.c

#define PY_SSIZE_T_CLEAN
#include <Python.h>

int
main(int argc, char *argv[])
{
    PyStatus status;
    PyConfig config;
    PyConfig_InitPythonConfig(&config);
    status = PyConfig_SetBytesString(&config, &config.program_name, argv[0]);
    if (PyStatus_Exception(status)) {
        goto exception;
    }
    status = Py_InitializeFromConfig(&config);
    if (PyStatus_Exception(status)) {
        goto exception;
    }
    PyConfig_Clear(&config);
    PyRun_SimpleString(
        "import sys\n"
        "from time import time,ctime\n"
        "print('Today is', ctime(time()))\n"
    );
    if (Py_FinalizeEx() < 0) {
        exit(120);
    }
    return 0;

  exception:
     PyConfig_Clear(&config);
     Py_ExitStatusException(status);
}

I decided to use gcc for this along with flake

flake.nix

{
  description = "A very basic python c api";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
  };

  outputs = { self, nixpkgs }: 
    let
      system = "x86_64-linux";
      pkgs = import nixpkgs { inherit system; };
    in {
    packages.${system}.default  =
      pkgs.stdenv.mkDerivation {
        name = "hello_python";
        src = self;
        nativeBuildInputs= with pkgs; [
          python313
          gcc
        ];
        buildPhase = ''
          gcc main.c -o hello_python
        '';
        installPhase = ''
          mkdir -p $out/bin; 
          install -t $out/bin hello_python
        '';
        NIX_CFLAGS_COMPILE = [
          "-I${pkgs.python313.outPath}/include/${pkgs.python313.executable}"
        ];
        NIX_LDFLAGS = [
          "-L${pkgs.python313.outPath}/lib"
        ];
      };
  };
}
$ tree
.
├── flake.lock
├── flake.nix
└── main.c

But when I tried to run it, I got a liker error

$ nix run .
error: Cannot build '/nix/store/q0l1ad8a2r0li5w2prhz8kxxvkdhj77q-hello_python.drv'.
       Reason: builder failed with exit code 1.
       Output paths:
         /nix/store/9vbjswb7q3n8csyf428fzdr6yzsk4vxw-hello_python
       Last 20 log lines:
       > Running phase: unpackPhase
       > unpacking source archive /nix/store/navqf7pq5f08byh7kmbq0f84lpm7l7sd-source
       > source root is source
       > Running phase: patchPhase
       > Running phase: updateAutotoolsGnuConfigScriptsPhase
       > Running phase: configurePhase
       > no configure script, doing nothing
       > Running phase: buildPhase
       > /nix/store/dc9vaz50jg7mibk9xvqw5dqv89cxzla3-binutils-2.44/bin/ld: /build/ccimUTjq.o: in function `main':
       > main.c:(.text.startup+0x32): undefined reference to `PyConfig_InitPythonConfig'
       > /nix/store/dc9vaz50jg7mibk9xvqw5dqv89cxzla3-binutils-2.44/bin/ld: main.c:(.text.startup+0x48): undefined reference to `PyConfig_SetBytesString'
       > /nix/store/dc9vaz50jg7mibk9xvqw5dqv89cxzla3-binutils-2.44/bin/ld: main.c:(.text.startup+0x6a): undefined reference to `PyStatus_Exception'
       > /nix/store/dc9vaz50jg7mibk9xvqw5dqv89cxzla3-binutils-2.44/bin/ld: main.c:(.text.startup+0x7d): undefined reference to `Py_InitializeFromConfig'
       > /nix/store/dc9vaz50jg7mibk9xvqw5dqv89cxzla3-binutils-2.44/bin/ld: main.c:(.text.startup+0x9f): undefined reference to `PyStatus_Exception'
       > /nix/store/dc9vaz50jg7mibk9xvqw5dqv89cxzla3-binutils-2.44/bin/ld: main.c:(.text.startup+0xaf): undefined reference to `PyConfig_Clear'
       > /nix/store/dc9vaz50jg7mibk9xvqw5dqv89cxzla3-binutils-2.44/bin/ld: main.c:(.text.startup+0xbd): undefined reference to `PyRun_SimpleStringFlags'
       > /nix/store/dc9vaz50jg7mibk9xvqw5dqv89cxzla3-binutils-2.44/bin/ld: main.c:(.text.startup+0xc2): undefined reference to `Py_FinalizeEx'
       > /nix/store/dc9vaz50jg7mibk9xvqw5dqv89cxzla3-binutils-2.44/bin/ld: main.c:(.text.startup+0xea): undefined reference to `PyConfig_Clear'
       > /nix/store/dc9vaz50jg7mibk9xvqw5dqv89cxzla3-binutils-2.44/bin/ld: main.c:(.text.startup+0x10c): undefined reference to `Py_ExitStatusException'
       > collect2: error: ld returned 1 exit status
       For full logs, run:
         nix log /nix/store/q0l1ad8a2r0li5w2prhz8kxxvkdhj77q-hello_python.drv

We are looking for an answer in Python - Official NixOS Wiki , C - NixOS Wiki and Cannot link against Python but I didn’t find anything.

Can someone help?

If anyone is curious, I managed to solve it.

{
  description = "A very basic python c api";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
  };
  outputs = { self, nixpkgs }: 
    let
      system = "x86_64-linux";
      pkgs = import nixpkgs { inherit system; };
    in {
    packages.${system}.default  =
      pkgs.stdenv.mkDerivation {
        name = "hello_python";
        src = self;
        nativeBuildInputs= with pkgs; [
          python313
          gcc
          autoPatchelfHook
        ];
        buildPhase = ''
          gcc main.c -o hello_python
        '';
        installPhase = ''
          mkdir -p $out/bin; 
          install -t $out/bin hello_python
        '';
        NIX_CFLAGS_COMPILE = [
          "-I${pkgs.python313.outPath}/include/${pkgs.python313.executable}"
        ];
        NIX_LDFLAGS = [
          "-L. -l:libpython3.13.so"
        ];
        # autoPatchelfIgnoreMissingDeps = [
        #   "libpython3.13.so"
        # ];
      };
  };
}

Put python in buildInputs, not nativeBuildInputs. Should take care of the include path and linker flags automatically. nativeBuildInputs is for inputs that are run during the build process (things that need to have been compiled for the build machine’s architecture when cross-compiling), so stdenv doesn’t try to include or link against them.

Also, gcc is included automatically in stdenv, so there’s no need to explicitly list it.

And why did you include autoPatchelfHook? That’s for when you’re packaging a prebuilt binary rather than compiling…

1 Like

Thanks, but practice has shown that there are errors in your statement.

  1. “Put python in buildInputs, not nativeBuildInputs.” Doesn’t work. I don’t know why but it doesn’t work.
  2. "gcc is included automatically.” You’re just right.
  3. “why did you include autoPatchelfHook?”. After I managed to compile the file. I encountered a problem.
$ nix run
/nix/store/69222wrf2g2szxa161v53wsgin9r254y-hello_python/bin/hello_python: error while loading shared libraries: libpython3.13.so.1.0: cannot open shared object file: No such file or directory

add autoPatchelfHook help.

Can you be more specific about 1? It should work. What kind of error do you get?

As for 3, this is fundamentally because you haven’t told stdenv that python is an input in the correct way. The fact that autoPatchelfHook fixes it is essentially an accident. This isn’t what it’s for, and it shouldn’t be used in a situation like this.

Overall, you seem to have found a bunch of brute-force workarounds to the fact that you haven’t made correct use of stdenv here. This should work with just buildInputs, buildPhase, and installPhase.

1 Like
pkgs.stdenv.mkDerivation {
        name = "hello_python";
        src = self;
        buildInputs= with pkgs; [
          python313
          # autoPatchelfHook
        ];
        buildPhase = ''
          gcc main.c -o hello_python
        '';
        installPhase = ''
          mkdir -p $out/bin; 
          install -t $out/bin hello_python
        '';
        # NIX_CFLAGS_COMPILE = [
        #   "-I${pkgs.python313.outPath}/include/${pkgs.python313.executable}"
        # ];
        # NIX_LDFLAGS = [
        #   "-L. -l:libpython3.13.so"
        # ];
      };
$ nix run
error: Cannot build '/nix/store/pnmiaz4ry46vgc4kn6rcqkl754jmvyhv-hello_python.drv'.
       Reason: builder failed with exit code 1.
       Output paths:
         /nix/store/jl75wbl009yl0lmc5kq9navqssrky0jb-hello_python
       Last 12 log lines:
       > Running phase: unpackPhase
       > unpacking source archive /nix/store/461gwgmc393k1rrpqa5ayck20p1igdhr-source
       > source root is source
       > Running phase: patchPhase
       > Running phase: updateAutotoolsGnuConfigScriptsPhase
       > Running phase: configurePhase
       > no configure script, doing nothing
       > Running phase: buildPhase
       > main.c:2:10: fatal error: Python.h: No such file or directory
       >     2 | #include <Python.h>
       >       |          ^~~~~~~~~~
       > compilation terminated.
       For full logs, run:
         nix log /nix/store/pnmiaz4ry46vgc4kn6rcqkl754jmvyhv-hello_python.drv


pkgs.stdenv.mkDerivation {
        name = "hello_python";
        src = self;
        buildInputs= with pkgs; [
          python313
          # autoPatchelfHook
        ];
        buildPhase = ''
          gcc main.c -o hello_python
        '';
        installPhase = ''
          mkdir -p $out/bin; 
          install -t $out/bin hello_python
        '';
        NIX_CFLAGS_COMPILE = [
          "-I${pkgs.python313.outPath}/include/${pkgs.python313.executable}"
        ];
        # NIX_LDFLAGS = [
        #   "-L. -l:libpython3.13.so"
        # ];
      };
$ nix run
error: Cannot build '/nix/store/3wy2ivsjis4k6j5m7pkdbhssmq58m7li-hello_python.drv'.
       Reason: builder failed with exit code 1.
       Output paths:
         /nix/store/nalaf8jwf1fv5g9vzba6mlkp4jf76d47-hello_python
       Last 20 log lines:
       > Running phase: unpackPhase
       > unpacking source archive /nix/store/2j4l8zj0w0yfnh8kabgqs3zlyjh957jz-source
       > source root is source
       > Running phase: patchPhase
       > Running phase: updateAutotoolsGnuConfigScriptsPhase
       > Running phase: configurePhase
       > no configure script, doing nothing
       > Running phase: buildPhase
       > /nix/store/dc9vaz50jg7mibk9xvqw5dqv89cxzla3-binutils-2.44/bin/ld:/build/ccBYD2d3.o: i
n function `main':
       > main.c:(.text.startup+0x32): undefined reference to`PyConfig_InitPythonConfig'
       > /nix/store/dc9vaz50jg7mibk9xvqw5dqv89cxzla3-binutils-2.44/bin/ld:main.c:(.text.startup+0x48): undefined reference to `PyConfig_SetBytesString'
       > /nix/store/dc9vaz50jg7mibk9xvqw5dqv89cxzla3-binutils-2.44/bin/ld: main.c:(.text.startup+0x6a): undefined reference to `PyStatus_Exception'
       > /nix/store/dc9vaz50jg7mibk9xvqw5dqv89cxzla3-binutils-2.44/bin/ld: main.c:(.text.startup+0x7d): undefined reference to `Py_InitializeFromConfig'
       > /nix/store/dc9vaz50jg7mibk9xvqw5dqv89cxzla3-binutils-2.44/bin/ld: main.c:(.text.startup+0x9f): undefined reference to `PyStatus_Exception'
       > /nix/store/dc9vaz50jg7mibk9xvqw5dqv89cxzla3-binutils-2.44/bin/ld: main.c:(.text.startup+0xaf): undefined reference to `PyConfig_Clear'
       > /nix/store/dc9vaz50jg7mibk9xvqw5dqv89cxzla3-binutils-2.44/bin/ld: main.c:(.text.startup+0xbd): undefined reference to `PyRun_SimpleStringFlags'
       > /nix/store/dc9vaz50jg7mibk9xvqw5dqv89cxzla3-binutils-2.44/bin/ld: main.c:(.text.startup+0xc2): undefined reference to `Py_FinalizeEx'
       > /nix/store/dc9vaz50jg7mibk9xvqw5dqv89cxzla3-binutils-2.44/bin/ld: main.c:(.text.startup+0xea): undefined reference to `PyConfig_Clear'
       > /nix/store/dc9vaz50jg7mibk9xvqw5dqv89cxzla3-binutils-2.44/bin/ld: main.c:(.text.startup+0x10c): undefined reference to `Py_ExitStatusException'
       > collect2: error: ld returned 1 exit status
       For full logs, run:
         nix log /nix/store/3wy2ivsjis4k6j5m7pkdbhssmq58m7li-hello_python.drv


pkgs.stdenv.mkDerivation {
        name = "hello_python";
        src = self;
        buildInputs= with pkgs; [
          python313
          # autoPatchelfHook
        ];
        buildPhase = ''
          gcc main.c -o hello_python
        '';
        installPhase = ''
          mkdir -p $out/bin; 
          install -t $out/bin hello_python
        '';
        NIX_CFLAGS_COMPILE = [
          "-I${pkgs.python313.outPath}/include/${pkgs.python313.executable}"
        ];
        NIX_LDFLAGS = [
          "-L. -l:libpython3.13.so"
        ];
      };

$ nix run
/nix/store/nyndmyvx4zyrbbns7jsnrwcz6ic3wnmm-hello_python/bin/hello_python: error while loading shared libraries: libpython3.13.so.1.0: cannot open shared object file: No such file or directory

pkgs.stdenv.mkDerivation {
        name = "hello_python";
        src = self;
        buildInputs= with pkgs; [
          python313
          autoPatchelfHook
        ];
        buildPhase = ''
          gcc main.c -o hello_python
        '';
        installPhase = ''
          mkdir -p $out/bin; 
          install -t $out/bin hello_python
        '';
        NIX_CFLAGS_COMPILE = [
          "-I${pkgs.python313.outPath}/include/${pkgs.python313.executable}"
        ];
        NIX_LDFLAGS = [
          "-L. -l:libpython3.13.so"
        ];
      };

$ nix run
Today is Sat Jan 17 11:48:44 2026

I can also list which specific flags are missing at specific stages, but I believe my brute-force methods are self-explanatory. Besides, it’s nix, if you want you can check it yourself.

Try passing $(pkg-config --cflags python --libs python) to your gcc invocation, or use a configuration system. Alternatively you can use pkg-config in the postConfig to export augmented but not completely overriden NIX_* envvars.

Though in my opinion CLI args should be prefered, or for projects of a certain size, proper configuration through autotools, cmake, or whatever else there is available.

2 Likes

Yeah, using pkg-config is probably the cleanest option in this case. The next-stage option would be a small makefile that uses pkg-config to set flag variables… which would just be moving the custom build and install phases into a different file, essentially. Then there’s full autotools, which is pretty clearly overkill.

1 Like

$(pkg-config --libs python) return -L/nix/store/3lll9y925zz9393sa59h653xik66srjb-python3-3.13.9/lib but this flag is already added twice by buildInputs. That’s why I added -l:libpython3.13.so instead and work

pkgs.stdenv.mkDerivation {
        name = "hello_python";
        src = self;
        buildInputs= with pkgs; [
          python313
          autoPatchelfHook
          pkg-config
        ];
        buildPhase = ''
          gcc $(pkg-config --cflags python) -l:libpython3.13.so \
          main.c -o hello_python
        '';
        installPhase = ''
          mkdir -p $out/bin; 
          install -t $out/bin hello_python
        '';
      };

If -L is given, the compiler knows where to look for shared objects. You still need -l to tell which ones to link.

More idiomatic would be -lpython3 or -lpython3.13. depending on which one you actually need.

1 Like