Skip to content

Commit c1a70b2

Browse files
committed
build: Remove build host Python from cross compiled environment
When installing a Python program with a shebang the shebang points to the native Python. Because the virtualenv builder symlinks packages to their original path, it means that we retain references to the native Python, bringing it into the runtime closure of a cross compiled application. By rewriting the shebang to the _target host_ Python at install time we can avoid references to the _build host_ Python in the runtime closure.
1 parent 4b74b01 commit c1a70b2

File tree

4 files changed

+74
-3
lines changed

4 files changed

+74
-3
lines changed

build/hooks/default.nix

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ let
3030

3131
pythonInterpreter =
3232
if stdenv.buildPlatform != stdenv.hostPlatform then
33-
"${crossPython}/bin/python"
33+
"${crossPython}/bin/${baseNameOf pythonOnBuildForHost.interpreter}"
3434
else
3535
pythonOnBuildForHost.interpreter;
3636

@@ -126,6 +126,26 @@ in
126126
} ./pyproject-output-setup-hook.sh
127127
) { };
128128

129+
/*
130+
Rewrite shebangs for cross compiled Python programs.
131+
132+
When cross compiling & installing a Python program the shebang gets written
133+
for the install-time Pythhon, which for cross compilation is for the _build host_.
134+
135+
This hook rewrites any shebangs pointing to the build host Python to the target host Python.
136+
*/
137+
pyprojectCrossShebangHook = callPackage (
138+
_:
139+
makeSetupHook {
140+
name = "pyproject-cross-shebang-hook";
141+
substitutions = {
142+
script = ./patch-cross-shebangs.py;
143+
inherit pythonInterpreter;
144+
hostInterpreter = python.interpreter;
145+
};
146+
} ./pyproject-cross-shebang-hook.sh
147+
) { };
148+
129149
/*
130150
Create a virtual environment from buildInputs
131151
@@ -136,7 +156,8 @@ in
136156
makeSetupHook {
137157
name = "pyproject-make-venv-hook";
138158
substitutions = {
139-
inherit pythonInterpreter python;
159+
pythonInterpreter = python.interpreter;
160+
inherit python;
140161
makeVenvScript = ./make-venv.py;
141162
};
142163
} ./pyproject-make-venv-hook.sh
@@ -156,6 +177,7 @@ in
156177
pyprojectBuildHook,
157178
pyprojectInstallHook,
158179
pyprojectOutputSetupHook,
180+
pyprojectCrossShebangHook,
159181
python,
160182
}:
161183
makeSetupHook {
@@ -167,7 +189,7 @@ in
167189
pyprojectBuildHook
168190
pyprojectInstallHook
169191
pyprojectOutputSetupHook
170-
];
192+
] ++ lib.optional (stdenv.buildPlatform != stdenv.hostPlatform) pyprojectCrossShebangHook;
171193
} ./meta-hook.sh
172194
)
173195
{

build/hooks/patch-cross-shebangs.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#!/usr/bin/env python
2+
import os
3+
import os.path
4+
import sys
5+
from pathlib import Path
6+
from typing import Optional
7+
8+
9+
def main():
10+
# Output directory
11+
out_dir = Path(os.environ["out"])
12+
bin_dir = out_dir.joinpath("bin")
13+
14+
# Cross python interpreter
15+
cross_bin = os.path.dirname(sys.executable)
16+
cross_shebang = f"#!{cross_bin}".encode()
17+
18+
# Target host interpreter
19+
host_bin = os.path.dirname(sys.argv[1])
20+
host_shebang = f"#!{host_bin}".encode()
21+
22+
if not bin_dir.exists():
23+
return
24+
25+
for bin in bin_dir.iterdir():
26+
script: Optional[bytes] = None
27+
28+
with bin.open(mode="rb") as fd:
29+
preamble = fd.read(len(cross_shebang))
30+
if preamble == cross_shebang:
31+
script = host_shebang + fd.read()
32+
33+
if script:
34+
print(f"Rewriting shebang for '{bin}'")
35+
36+
with bin.open(mode="wb") as fd:
37+
fd.write(script)
38+
39+
40+
if __name__ == "__main__":
41+
main()
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
pyprojectCrossShebangHook() {
2+
@pythonInterpreter@ @script@ @hostInterpreter@
3+
}
4+
5+
if [ -z "${dontUsePyprojectCrossShebangHook-}" ]; then
6+
preFixupPhases+=" pyprojectCrossShebangHook"
7+
fi

build/packages.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ let
8484
pyprojectInstallHook
8585
pyprojectBytecodeHook
8686
pyprojectOutputSetupHook
87+
pyprojectCrossShebangHook
8788
pyprojectMakeVenvHook
8889
pyprojectHook
8990
pyprojectWheelHook

0 commit comments

Comments
 (0)