Skip to content

Conversation

@dyegoaurelio
Copy link
Contributor

On NixOS, dynamically linked binaries from the Python venv (installed via uv/pip) cannot run directly because they expect standard Linux library paths that don't exist on NixOS.

This commit adds FHS wrappers that allow these binaries to run in an FHS-compatible environment at runtime, without patching them.

I considered using the nixpkgs versions of ruff and pyrefly directly, or overriding their derivations to match the versions in requirements.txt. However, decided against it because:

  • Version mismatches between nixpkgs and requirements.txt caused type checking incompatibilities (pyrefly 0.34.0 vs 0.23.1)
  • Building these tools from source in nix is slow and adds significant time to nix-shell initialization (both are rust packages that take quite some time to build)

Testing: just improvements to the NixOS development environment, no test needed

Before:

➜ servo (main) ✔ nix-shell
➜ servo (main) ✔ ./mach fmt
Could not start dynamically linked executable: /home/dyego/coding/random/servo/.venv/bin/ruff
NixOS cannot run dynamically linked executables intended for generic
linux environments out of the box. For more information, see:
https://nix.dev/permalink/stub-ld
➜ servo (main) ✔ ./mach test-tidy
 ➤  Checking config file (./servo-tidy.toml)...
 ➤  Checking directories for correct file extensions...
Could not start dynamically linked executable: ruff
NixOS cannot run dynamically linked executables intended for generic
linux environments out of the box. For more information, see:
https://nix.dev/permalink/stub-ld
Error running mach:

    ['test-tidy']

The error occurred in code that was called by the mach command. This is either
a bug in the called code itself or in the way that mach is calling it.
You can invoke |./mach busted| to check if this issue is already on file. If it
isn't, please use |./mach busted file| to report it. If |./mach busted| is
misbehaving, you can also inspect the dependencies of bug 1543241.

If filing a bug, please include the full output of mach, including this error
message.

The details of the failure are as follows:

json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

  File "/home/dyego/coding/random/servo/python/servo/testing_commands.py", line 322, in test_tidy
    tidy_failed = tidy.scan(not all_files, not no_progress, github_annotations)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dyego/coding/random/servo/python/tidy/tidy.py", line 919, in scan
    for error in errors:
  File "/home/dyego/coding/random/servo/python/tidy/tidy.py", line 401, in check_ruff_lints
    for error in json.loads(e.output):
                 ^^^^^^^^^^^^^^^^^^^^
  File "/nix/store/2g9b898aq9kmizmhmhbdip5mixrc5wrk-python3-3.11.14/lib/python3.11/json/__init__.py", line 346, in loads
    return _default_decoder.decode(s)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/nix/store/2g9b898aq9kmizmhmhbdip5mixrc5wrk-python3-3.11.14/lib/python3.11/json/decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/nix/store/2g9b898aq9kmizmhmhbdip5mixrc5wrk-python3-3.11.14/lib/python3.11/json/decoder.py", line 355, in raw_decode
    raise JSONDecodeError("Expecting value", s, err.value) from None
➜ servo (main) ✔

(note that the JSONDecodeError is because we're trying to parse the "NixOS cannot run dynamically linked executables intended..." string as JSON)

Now:

➜ servo (fix-nix-mach) ✔ nix-shell
➜ servo (fix-nix-mach) ✔ ./mach fmt
➜ servo (fix-nix-mach) ✔ ./mach test-tidy
 ➤  Checking config file (./servo-tidy.toml)...
 ➤  Checking directories for correct file extensions...
 ➤  Checking type annotations in python files ...
 ➤  Skipping WPT lint checks, because no relevant files changed.
 ➤  Running `cargo-deny` checks...
 ➤  Checking formatting of Rust files...
 ➤  Checking formatting of python files...
 ➤  Checking formatting of toml files...

 ✅ test-tidy reported no errors.
➜ servo (fix-nix-mach) ✔

@servo-highfive servo-highfive added the S-awaiting-review There is new code that needs to be reviewed. label Nov 24, 2025
@delan delan requested a review from mukilan November 24, 2025 14:15
@mukilan
Copy link
Member

mukilan commented Nov 25, 2025

Thank you! This is a really cool solution and it would help us fix #37288.

I've tested this on my machine and seems to work well. I just have one observation - running ./mach test-tidy will fail when it is run as the first command in a fresh environment (one with no .venv folderr) , because the uv installed versions in .venv won't exist when we enter the shell. This is a corner case, as it unlikely test-tidy or fmt will be run before ./mach build so I think this PR can still be merged as is.

But I wonder if there is a way to fix this so that it always works, maybe using uv tool install pyrefly --constraints=python/requirement.txt when the FHS is created. I am not that familiar with buildFHSEnv to say if that is possible or not.

On NixOS, dynamically linked binaries from the Python venv (installed
via uv/pip) cannot run directly because they expect standard Linux
library paths that don't exist on NixOS.

This commit adds FHS wrappers that allow these binaries to run in
an FHS-compatible environment at runtime, without patching them.

I considered using the nixpkgs versions of ruff and pyrefly directly,
or overriding their derivations to match the versions in
requirements.txt. However, decided against it because:
- Version mismatches between nixpkgs and requirements.txt caused type
  checking incompatibilities (pyrefly 0.34.0 vs 0.23.1)
- Building these tools from source in nix is slow and adds significant
  time to nix-shell initialization (both are rust packages that take quite some time to build)

Signed-off-by: Dyego Aurélio <dyegoaurelio@gmail.com>
@dyegoaurelio
Copy link
Contributor Author

This issue was because activating the virtualenv was overriding the path again.

fixed it by adding the nix logic at _activate_virtualenv

➜ servo (fix-nix-mach) ✗ nix-shell --pure

[nix-shell:~/coding/random/servo]$ rm -rf .venv    

[nix-shell:~/coding/random/servo]$ ./mach test-tidy
 * Setting up virtual environment...
 * Installing Python requirements...
 ➤  Checking config file (./servo-tidy.toml)...
 ➤  Checking directories for correct file extensions...
 ➤  Checking files for tidiness...
 ➤  Checking type annotations in python files ...
 ➤  Skipping WPT lint checks, because no relevant files changed.
 ➤  Running `cargo-deny` checks...
 ➤  Checking formatting of Rust files...
 ➤  Checking formatting of python files...
 ➤  Checking formatting of toml files...

 ✅ test-tidy reported no errors.

[nix-shell:~/coding/random/servo]$ rm -rf .venv

[nix-shell:~/coding/random/servo]$ ./mach fmt
 * Setting up virtual environment...
 * Installing Python requirements...

[nix-shell:~/coding/random/servo]$ 

@mukilan
Copy link
Member

mukilan commented Nov 25, 2025

This issue was because activating the virtualenv was overriding the path again.

Ah, nice find!

Copy link
Member

@mukilan mukilan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@servo-highfive servo-highfive removed the S-awaiting-review There is new code that needs to be reviewed. label Nov 25, 2025
@mukilan mukilan added this pull request to the merge queue Nov 25, 2025
@servo-highfive servo-highfive added the S-awaiting-merge The PR is in the process of compiling and running tests on the automated CI. label Nov 25, 2025
Merged via the queue into servo:main with commit bc81a4f Nov 25, 2025
32 checks passed
@servo-highfive servo-highfive removed the S-awaiting-merge The PR is in the process of compiling and running tests on the automated CI. label Nov 25, 2025
@dyegoaurelio dyegoaurelio deleted the fix-nix-mach branch November 25, 2025 16:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants