How to discover nix binaries in python

I’m trying to build a nix package (via buildPythonPackage) for ufw, which is essentially a wrapper for iptables written in Python.

The recommended way of installing this program (without using snapcraft) is to use the provided setup.py file, which, like many install scripts, looks for the executables the program will need to do its work.

The problem is that the setup.py file only looks in things like /usr/sbin, /sbin, /bin, and other such places, but not /nix/store.

I understand I can use the patches attribute to modify the source code, but I’m not sure how to actually expose python to the correct paths where it could find iptables. Letting it loose on /nix/store to just recurse through everything feels like a nonstarter.

Do I get the script to iterate through NIX_PATH variable? Do I to coerce python to traverse symlinks and just tell it to look in /bin? Do I set an env var at install time set to ${pkgs.getExe pkgs.iptables}, and then feed that into the script?

I’d love some insight here on the “nix appropriate” way to tell Python where the heck to find certain resources.

1 Like

Typically, adding the relevant package to propagatedBuildInputs is sufficient to allow the python code to find the binary. If that isn’t working here, they’re doing something stranger than normal.

1 Like

Good call, I had iptables as a part of the buildInputs, but not propogatedBuildInputs. That’s worth adding.

But yeah I think they also have a bit of a nonstandard way of looking for binaries.

They have a list of all of the possible locations, and they use os.path and os.join to find the specific files. It sorta feels like I should be doing something else. It’s a sort of Debian leaning solution, which makes sense given the publisher.

From setup.py

for e in ['iptables']:
    # Historically iptables was in /sbin, then later also symlinked from
    # /usr/sbin/iptables to /sbin/iptables. Debian bullseye moves iptables
    # to /usr/sbin with no symlink in /sbin except on upgrades. To accomodate
    # buildds that may still have the old iptables, search /usr/sbin first
    for dir in ['/usr/sbin', '/sbin', '/usr/bin', '/bin', '/usr/local/sbin', \
                '/usr/local/bin']:
        if e == "iptables":
            if os.path.exists(os.path.join(dir, e)):
                iptables_dir = dir
                iptables_exe = os.path.join(iptables_dir, "iptables")
                print("Found '%s'" % iptables_exe)
            else:
                continue

        if iptables_exe != "":
            break


if iptables_exe == '':
    print("ERROR: could not find required binary 'iptables'", file=sys.stderr)
    sys.exit(1)

Ugh. They completely ignore PATH. That’s certainly going to be a problem for nix packaging. I doubt you can avoid patching that.

Should be enough to replace that hardcoded list of directories with something that lists the entries in PATH instead.

Or you could just use nix string antiquotes to plug the store path of the iptables package directly in there in the patch.

1 Like

Yeah, I think you’re right. I was messing around with env variables for a minute before I was like “hold up this feels like overkill”.

But I just can’t see any other path forward. Plugging in PATH or NIX_PATH feels like the lowest effort option, so I’ll try that.

It’ll have to be a patch no matter what happens.

1 Like