Sqlalchemy python fails to find libstdc++.so.6 in virtualenv

Hi, I’m having trouble understanding how gcc libraries are being linked when Python virtual environments are being used in nixos. I have this in my configuration.nix:

environment.systemPackages = with pkgs; [
     (python311.withPackages(ps: with ps; [ 
       ipython
       setuptools
       sqlalchemy
       greenlet
       aiosqlite
       virtualenv
     ]))
     poetry
     pdm
     gcc

then any virtualenvironments created with poetry or pdm with the same packages throw this error when sqlalchemy is used with asyncio:

<sqlalchemy.orm.session.AsyncSession object at 0x7f33a43d8410>
Traceback (most recent call last):
  File "/home/pythonapp/sqa.py", line 12, in <module>
    asyncio.run(get_db())
  File "/nix/store/qp5zys77biz7imbk6yy85q5pdv7qk84j-python3-3.11.6/lib/python3.11/asyncio/runners.py", line 190, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "/nix/store/qp5zys77biz7imbk6yy85q5pdv7qk84j-python3-3.11.6/lib/python3.11/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/nix/store/qp5zys77biz7imbk6yy85q5pdv7qk84j-python3-3.11.6/lib/python3.11/asyncio/base_events.py", line 653, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "/home/pythonapp/sqa.py", line 9, in get_db
    async with async_session() as session:
  File "/home/pythonapp/__pypackages__/3.11/lib/sqlalchemy/ext/asyncio/session.py", line 1082, in __aexit__
    await asyncio.shield(task)
  File "/home/pythonapp/__pypackages__/3.11/lib/sqlalchemy/ext/asyncio/session.py", line 1027, in close
    await greenlet_spawn(self.sync_session.close)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/pythonapp/__pypackages__/3.11/lib/sqlalchemy/util/concurrency.py", line 64, in greenlet_spawn
    _not_implemented()
  File "/home/pythonapp/__pypackages__/3.11/lib/sqlalchemy/util/concurrency.py", line 44, in _not_implemented
    raise ValueError(
ValueError: the greenlet library is required to use this function. libstdc++.so.6: cannot open shared object file: No such file or directory

I have gcc installed and the c++ file exists in multiple nix stores:

❯ : fd -g libstdc++.so.6 /nix/store/
/nix/store/r7wq6fn906m2fr5jnmhg7774h2cs0440-gcc-12.3.0-lib/lib/libstdc++.so.6
.... more similar locations

To replicate this a simple script can be used:

import asyncio
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker, declarative_base

engine = create_async_engine("sqlite+aiosqlite://./test.db", echo=True)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)
async def get_db():
    async with async_session() as session:
        print(session)

asyncio.run(get_db())

What’s the process to expose my virtualenv to libstdc++.so.6 here?
I’ve tried using poetry2nix through nix flakes but there are other bugs that are getting in the way there (like this one)

Another alternative I’ve seen is to edit LD_LIBRARY_PATH explicitly which doesn’t seem very sustainable.

Figured this out. There’s this detailed write up here on github how to get this done using nix-ld but here’s a quick summary:

  1. Enable nix-ld in your configuration.nix:
  programs.nix-ld.enable = true;
  programs.nix-ld.libraries = with pkgs; [
    zlib # numpy
    libgcc  # sqlalchemy
    # that's where the shared libs go, you can find which one you need using 
    # nix-locate --top-level libstdc++.so.6  (replace this with your lib)
    # ^ this requires `nix-index` pkg
  ];
  1. create a nix shell in your project to expand LD_LIBRARY_PATH with the shared libs. e.g. default.nix
with import <nixpkgs> {};
mkShell {
  NIX_LD_LIBRARY_PATH = lib.makeLibraryPath [
    stdenv.cc.cc
  ];
  NIX_LD = lib.fileContents "${stdenv.cc}/nix-support/dynamic-linker";
  shellHook = ''
    export LD_LIBRARY_PATH=$NIX_LD_LIBRARY_PATH
  '';
}
  1. Then nix-shell and voila, the python scripts should work!

Still not sure I understand the whole process but this seems like the nix way of doing this.

2 Likes

Thank you for posting your solution, however I’m having trouble implementing it for my situation.

I’m using Nix at work, and I created a development flake for an old Python app that my team and I have to maintain.

I had it working properly, but then my team needed to add a new Python library and now the flake doesn’t work on my NixOS system.

After searching for the error ImportError: libstdc++.so.6: cannot open shared object file: No such file or directory, it seems that this is related to the challenge of using non-Nix Python packages with binaries on NixOS.

I found your post above which describes how to use nix-ld to get around this. However, your post is using nix-shell, and I am using a flake, so some things are different.

Here is the development flake. The new part I added starts on the line with NIX_LD_LIBRARY_PATH. Any suggestions?

{
  description = "This Nix flake creates a development shell for the legacy customer api that provides the required versions of Python 2.7 and its associated packages: certifi, pip, and virtualenv. It also provides Python 3.10 which is required for Google App Engine.";
  inputs = {
    flake-utils.url = "github:numtide/flake-utils";
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
    nixpkgs-py27.url = "github:nixos/nixpkgs/272744825d28f9cea96fe77fe685c8ba2af8eb12";
  };

  outputs = {
    flake-utils,
    nixpkgs,
    nixpkgs-py27,
    ...
  }@inputs:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = nixpkgs.legacyPackages.${system};

      in
      {
        devShells.default = pkgs.mkShell {
          buildInputs = [
            (nixpkgs.lib.hiPrio (inputs.nixpkgs-py27.legacyPackages.${pkgs.system}.python2.withPackages (ps: [
              ps.certifi ps.pip ps.setuptools ps.virtualenv ps.grpcio
            ])))
            pkgs.bashInteractive
            pkgs.python310
          ];
          NIX_LD_LIBRARY_PATH = nixpkgs.lib.makeLibraryPath [
            pkgs.stdenv.cc.cc
          ];
          NIX_LD = nixpkgs.lib.fileContents "${pkgs.stdenv.cc}/nix-support/dynamic-linker";
          shellHook = ''
            export LD_LIBARY_PATH=$NIX_LD_LIBRARY_PATH
          '';
        };
      });
}

The flake fails with this output:

error:
       … while calling the 'derivationStrict' builtin

         at /builtin/derivation.nix:9:12: (source not available)

       … while evaluating derivation 'nix-shell'
         whose name attribute is located at /nix/store/d2s8k5b83sqabpqnaikrzpl64p5w85ns-source/pkgs/stdenv/generic/make-derivation.nix:331:7

       … while evaluating attribute 'NIX_LD' of derivation 'nix-shell'

         at /nix/store/841badlw8byimh9dfz0nr8gvagdwnvxg-source/flake.nix:36:9:

           35|         ];
           36|         NIX_LD = nixpkgs.lib.fileContents "${pkgs.stdenv.cc}/nix-support/dynamic-linker";
             |         ^
           37|         shellHook = ''

       error: access to canonical path '/nix/store/m3gq2f6iwh3g67zrs27m7w82b37l8gf8-binutils-wrapper-2.41/nix-support/dynamic-linker' is forbidden in restricted mode

I found suggestions to use nix develop --impure, which loads without error. But the original error occurs when trying to run the Python app:

    from grpc._cython import cygrpc as _cygrpc
ImportError: libstdc++.so.6: cannot open shared object file: No such file or directory

I have packaged a few Python projects using Nix on non-NixOS systems and haven’t used this NIX_LD technique. I’m guessing that works well on NixOS but may not as well in other systems.
In some cases, I’ve been able to simply export the LD_LIBARY_PATH variable.

Here is an example of your flake that works for me:

{
  description = "This Nix flake creates a development shell for the legacy customer api that provides the required versions of Python 2.7 and its associated packages: certifi, pip, and virtualenv. It also provides Python 3.10 which is required for Google App Engine.";
  inputs = {
    flake-utils.url = "github:numtide/flake-utils";
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
    nixpkgs-py27.url = "github:nixos/nixpkgs/272744825d28f9cea96fe77fe685c8ba2af8eb12";
  };

  outputs =
    { flake-utils
    , nixpkgs
    , nixpkgs-py27
    , ...
    }@inputs:
    flake-utils.lib.eachDefaultSystem (system:
    let
      pkgs = nixpkgs.legacyPackages.${system};

    in
    {
      devShells.default = pkgs.mkShell {
        buildInputs = [
          (nixpkgs.lib.hiPrio (inputs.nixpkgs-py27.legacyPackages.${pkgs.system}.python2.withPackages (ps: [
            ps.certifi
            ps.pip
            ps.setuptools
            ps.virtualenv
            ps.grpcio
          ])))
          pkgs.bashInteractive
          pkgs.python310
        ];
        shellHook = ''
          export LD_LIBARY_PATH=${pkgs.stdenv.cc.cc.lib}/lib/
        '';
      };
    });
}

caveats

Sometimes packaging python projects with nix has taken me down the rabbit hole. Especially with cython and older versions of python. Quite fun! It has required me fiddling with knobs to balance what I find in nixpkgs versus a virtualenv.

Hi, thanks for your reply.

I tried using the flake as you posted.

Unfortunately, the Python app, which is relying on google devappserver, fails to start with the following error: ImportError: libstdc++.so.6: cannot open shared object file: No such file or directory

It looks like there’s an issue with the grpc dependency.

Full stack trace below:

Traceback (most recent call last):
  File "/home/lumbar/bd/customer-api-legacy/.venv/google-cloud-sdk/platform/google_appengine/python27/sdk/google/appengine/runtime/wsgi.py", line 238, in Handle
    handler = _config_handle.add_wsgi_middleware(self._LoadHandler())
  File "/home/lumbar/bd/customer-api-legacy/.venv/google-cloud-sdk/platform/google_appengine/python27/sdk/google/appengine/runtime/wsgi.py", line 309, in _LoadHandler
    handler, path, err = LoadObject(self._handler)
  File "/home/lumbar/bd/customer-api-legacy/.venv/google-cloud-sdk/platform/google_appengine/python27/sdk/google/appengine/runtime/wsgi.py", line 83, in LoadObject
    obj = __import__(path[0])
  File "/home/lumbar/bd/customer-api-legacy/api/__init__.py", line 1, in <module>
    from .main import app
  File "/home/lumbar/bd/customer-api-legacy/api/main.py", line 24, in <module>
    from google.cloud import ndb
  File "/home/lumbar/bd/customer-api-legacy/lib/google/cloud/ndb/__init__.py", line 28, in <module>
    from google.cloud.ndb.client import Client
  File "/home/lumbar/bd/customer-api-legacy/lib/google/cloud/ndb/client.py", line 18, in <module>
    import grpc
  File "/home/lumbar/bd/customer-api-legacy/lib/grpc/__init__.py", line 23, in <module>
    from grpc._cython import cygrpc as _cygrpc
ImportError: libstdc++.so.6: cannot open shared object file: No such file or directory
1 Like

Ah, thanks for following up. I’ll try to diagnose further sometime later today :slight_smile:

Actually, try nobbz’s advice and set the flags using an attribute of mkShell: How to solve libstdc++ not found in shell.nix - #9 by NobbZ

I also realized I cannot troubleshoot further without a simplified python example invoking grpc.

I tried following the advice in that thread which is to add LD_LIBRARY_PATH = "${pkgs.stdenv.cc.cc.lib}/lib";, so now my flake looks like this:

{
  description = "This Nix flake creates a development shell for the legacy customer api that provides the required versions of Python 2.7 and its associated packages: certifi, pip, and virtualenv. It also provides Python 3.10 which is required for Google App Engine.";
  inputs = {
    flake-utils.url = "github:numtide/flake-utils";
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
    nixpkgs-py27.url = "github:nixos/nixpkgs/272744825d28f9cea96fe77fe685c8ba2af8eb12";
  };

  outputs = { flake-utils, nixpkgs, nixpkgs-py27, ... }@inputs:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = nixpkgs.legacyPackages.${system};
      in
      {
        devShells.default = pkgs.mkShell {
          buildInputs = [
            (nixpkgs.lib.hiPrio (inputs.nixpkgs-py27.legacyPackages.${pkgs.system}.python2.withPackages (ps: [ ps.certifi ps.pip ps.setuptools ps.virtualenv ])))
            pkgs.python310
            pkgs.bashInteractive
            pkgs.busybox
          ];
          LD_LIBRARY_PATH = "${pkgs.stdenv.cc.cc.lib}/lib";
          shellHook = ''
            export IJSON_BACKEND=python
          '';
        };
      });
}

Now, when I start the app, it no longer shows the above error but instead shows this new error. Maybe this is progress:

/home/lumbar/bd/legacy/.venv/bin/python: /nix/store/a6rnjp15qgp8a699dlffqj94hzy1nldg-glibc-2.32/lib/libc.so.6: version `GLIBC_2.35' not found (required by /nix/store/4zp05qkzh9s3c3k7pp9d36vhiz3x8h9l-gcc-13.2.0-lib/lib/libgcc_s.so.1)
/home/lumbar/bd/legacy/.venv/bin/python: /nix/store/a6rnjp15qgp8a699dlffqj94hzy1nldg-glibc-2.32/lib/libc.so.6: version `GLIBC_2.34' not found (required by /nix/store/4zp05qkzh9s3c3k7pp9d36vhiz3x8h9l-gcc-13.2.0-lib/lib/libgcc_s.so.1)

You asked for an example of grpc being invoked. Unfortunately, there is nothing like that in the code I’m working on. But grpc is a dependency of the Google App Engine sdk that we are stuck using for the time being. If it provides any info, here is a more complete stack trace of the previous error that occurred before adding LD_LIBRARY_PATH to the flake:

Traceback (most recent call last):
  File "/home/lumbar/bd/legacy/.venv/google-cloud-sdk/platform/google_appengine/python27/sdk/google/appengine/runtime/wsgi.py", line 238, in Handle
    handler = _config_handle.add_wsgi_middleware(self._LoadHandler())
  File "/home/lumbar/bd/legacy/.venv/google-cloud-sdk/platform/google_appengine/python27/sdk/google/appengine/runtime/wsgi.py", line 309, in _LoadHandler
    handler, path, err = LoadObject(self._handler)
  File "/home/lumbar/bd/legacy/.venv/google-cloud-sdk/platform/google_appengine/python27/sdk/google/appengine/runtime/wsgi.py", line 83, in LoadObject
    obj = __import__(path[0])
  File "/home/lumbar/bd/legacy/api/__init__.py", line 1, in <module>
    from .main import app
  File "/home/lumbar/bd/legacy/api/main.py", line 24, in <module>
    from google.cloud import ndb
  File "/home/lumbar/bd/legacy/lib/google/cloud/ndb/__init__.py", line 28, in <module>
    from google.cloud.ndb.client import Client
  File "/home/lumbar/bd/legacy/lib/google/cloud/ndb/client.py", line 18, in <module>
    import grpc
  File "/home/lumbar/bd/legacy/lib/grpc/__init__.py", line 23, in <module>
    from grpc._cython import cygrpc as _cygrpc
ImportError: libstdc++.so.6: cannot open shared object file: No such file or directory