diff --git a/cpython-unix/build-cpython.sh b/cpython-unix/build-cpython.sh index 7deb00830..e908c0f32 100755 --- a/cpython-unix/build-cpython.sh +++ b/cpython-unix/build-cpython.sh @@ -1063,6 +1063,20 @@ replace_in_all("-L%s/deps/lib" % tools_path, "") # See https://github.com/python/cpython/issues/145810#issuecomment-4068139183 replace_in_all("-LModules/_hacl", "") +# Strip toolchain bin directory prefix from sysconfig artifacts to avoid exposing +# build-time paths. This primarily fixes AR (e.g., /tools/llvm/bin/llvm-ar -> llvm-ar), +# which is not normalized by CPython, and also catches RANLIB and similar tools. +# See https://github.com/astral-sh/python-build-standalone/issues/1073 +toolchain = os.environ["TOOLCHAIN"] +toolchain_bin = os.path.normpath(os.path.join(tools_path, toolchain, "bin")) +toolchain_bin_norm = toolchain_bin + "/" +replace_in_all(toolchain_bin_norm, "") + +# Also catch resolved symlinks (e.g., macOS /var vs /private/var). +toolchain_bin_real = os.path.realpath(toolchain_bin) + "/" +if toolchain_bin_real != toolchain_bin_norm: + replace_in_all(toolchain_bin_real, "") + EOF ${BUILD_PYTHON} "${ROOT}/hack_sysconfig.py" "${ROOT}/out/python" diff --git a/test_ar_normalization.py b/test_ar_normalization.py new file mode 100644 index 000000000..e64883f2d --- /dev/null +++ b/test_ar_normalization.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +"""Test that AR normalization works correctly.""" +import os + +def test_ar_normalization(): + """Simulate the string replacement to verify it works.""" + # Simulated sysconfig data + test_cases = [ + # (input, expected_output, description) + ("AR = /tools/llvm/bin/llvm-ar", "AR = llvm-ar", "Basic AR case"), + ("RANLIB = /tools/llvm/bin/llvm-ranlib", "RANLIB = llvm-ranlib", "RANLIB case"), + ("CC = /tools/llvm/bin/clang -pthread", "CC = clang -pthread", "CC with flags"), + ("CXX = /tools/llvm/bin/clang++ -pthread", "CXX = clang++ -pthread", "CXX with flags"), + # Edge cases + ("PATH=/tools/llvm/bin:/usr/bin", "PATH=/tools/llvm/bin:/usr/bin", "PATH unchanged (no trailing /)"), + ("Some random text", "Some random text", "No match - unchanged"), + # Path normalization: our search pattern is normalized, so double-slash + # in sysconfig data won't match (but that's OK - sysconfig should be normalized) + ("AR = /tools//llvm/bin/llvm-ar", "AR = /tools//llvm/bin/llvm-ar", "Double slash in data won't match (expected)"), + # Empty value after replacement (edge case) + ("AR = /tools/llvm/bin/", "AR = ", "Trailing slash creates empty value (acceptable)"), + ] + + tools_path = "/tools" + toolchain = "llvm" + + # Use os.path.normpath to handle edge cases like double slashes + toolchain_bin = os.path.normpath(os.path.join(tools_path, toolchain, "bin")) + search = toolchain_bin + "/" + replace = "" + + print(f"Testing replacement: '{search}' -> '{replace}'") + print("-" * 60) + + all_passed = True + for input_str, expected, description in test_cases: + output = input_str.replace(search, replace) + passed = output == expected + status = "✓" if passed else "✗" + + print(f"{status} {description}") + if not passed: + print(f" Input: {input_str}") + print(f" Expected: {expected}") + print(f" Got: {output}") + all_passed = False + + print("-" * 60) + if all_passed: + print("All tests passed!") + return 0 + else: + print("Some tests failed!") + return 1 + +if __name__ == "__main__": + import sys + sys.exit(test_ar_normalization())