From 8adb94c974cd14f379f64cb62159bf40b9c21808 Mon Sep 17 00:00:00 2001 From: Manu Garg Date: Sat, 3 Oct 2026 00:40:59 -0700 Subject: [PATCH 1/2] fix: honor CC/AR and handle missing toolchain when building from sdist setup.py took the compiler and archiver straight from Python's sysconfig. On a cross-compiled Python (e.g. SynoCommunity's) that is the build machine's toolchain, which doesn't exist where pip runs, and there was no way to override it (#263). Use the CC/AR environment variables first, skip a sysconfig tool that isn't runnable, and fail with a clear message if no toolchain is found. Also don't crash in the version lookup when git isn't installed. --- src/pymod/setup.py | 53 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/src/pymod/setup.py b/src/pymod/setup.py index 9939338..c56d3e7 100644 --- a/src/pymod/setup.py +++ b/src/pymod/setup.py @@ -76,11 +76,18 @@ def git_version(): ) +def in_git_repo(): + try: + return subprocess.call( + "git rev-parse --git-dir".split(" "), stderr=subprocess.DEVNULL + ) == 0 + except OSError: + # git is not installed. + return False + + def pacparser_version(): - if ( - subprocess.call("git rev-parse --git-dir".split(" "), stderr=subprocess.DEVNULL) - == 0 - ): + if in_git_repo(): return git_version() # Check if we have version.mk. It's added in the manual release tarball @@ -176,14 +183,48 @@ def run(self): shutil.rmtree("quickjs") +def find_build_tool(var, default): + """Return the command (as an argv list) for the build tool named by var. + + The environment variable wins, then Python's sysconfig value, then the + default. Cross-compiled Pythons record the build machine's toolchain in + sysconfig, which usually doesn't exist where the module is being + installed, so a sysconfig value that isn't runnable is skipped. + """ + if os.environ.get(var): + cmd = shlex.split(os.environ[var]) + if not shutil.which(cmd[0]): + sys.exit("%s is set to '%s', but '%s' was not found." % + (var, os.environ[var], cmd[0])) + return cmd + + cmd = shlex.split(sysconfig.get_config_var(var) or "") + if cmd and shutil.which(cmd[0]): + return cmd + + if not shutil.which(default): + tried = "'%s'" % default + if cmd: + tried = "'%s' (from Python's build configuration) or %s" % ( + cmd[0], tried) + sys.exit( + "Building pacparser from source needs a C toolchain, but %s was " + "not found. Install one, or point the %s environment variable at " + "it." % (tried, var)) + # Export the fallback so that setuptools compiles the extension itself + # with the same tool instead of the unusable sysconfig one. + os.environ[var] = default + return [default] + + def build_c_objects(): """Compile pacparser.o and quickjs/libquickjs.a from the C sources. The sdist ships the C sources but no prebuilt objects, so they have to be built before the _pacparser extension can be linked. """ - cc = shlex.split(sysconfig.get_config_var("CC") or "cc") - ar = shlex.split(sysconfig.get_config_var("AR") or "ar") + cc = find_build_tool("CC", "cc") + ar = find_build_tool("AR", "ar") if not os.path.exists(os.path.join("quickjs", "libquickjs.a")): quickjs_obj = os.path.join("quickjs", "quickjs.o") if not os.path.exists(quickjs_obj): From 21a9b6b927bd9cecaafbd37a10dad38d474420eb Mon Sep 17 00:00:00 2001 From: Manu Garg Date: Sat, 3 Oct 2026 00:47:27 -0700 Subject: [PATCH 2/2] Address review: resolve CC for every build, look up ar only when needed - Resolve the compiler whenever a build command runs, not only when the C objects are missing, so a re-run with the objects already present still gets a working compiler exported for setuptools. - Only look up ar when libquickjs.a actually has to be created. - Don't raise IndexError on a whitespace-only CC/AR. --- src/pymod/setup.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/pymod/setup.py b/src/pymod/setup.py index c56d3e7..2d64ffb 100644 --- a/src/pymod/setup.py +++ b/src/pymod/setup.py @@ -191,8 +191,8 @@ def find_build_tool(var, default): sysconfig, which usually doesn't exist where the module is being installed, so a sysconfig value that isn't runnable is skipped. """ - if os.environ.get(var): - cmd = shlex.split(os.environ[var]) + cmd = shlex.split(os.environ.get(var, "")) + if cmd: if not shutil.which(cmd[0]): sys.exit("%s is set to '%s', but '%s' was not found." % (var, os.environ[var], cmd[0])) @@ -217,15 +217,14 @@ def find_build_tool(var, default): return [default] -def build_c_objects(): +def build_c_objects(cc): """Compile pacparser.o and quickjs/libquickjs.a from the C sources. The sdist ships the C sources but no prebuilt objects, so they have to be built before the _pacparser extension can be linked. """ - cc = find_build_tool("CC", "cc") - ar = find_build_tool("AR", "ar") if not os.path.exists(os.path.join("quickjs", "libquickjs.a")): + ar = find_build_tool("AR", "ar") quickjs_obj = os.path.join("quickjs", "quickjs.o") if not os.path.exists(quickjs_obj): subprocess.check_call( @@ -266,13 +265,19 @@ def main(patched_func): # that other commands (sdist, egg_info, clean, ...) do not require a # C toolchain. build_commands = ("build", "build_ext", "bdist", "bdist_wheel", "install") + building = sys.platform != "win32" and any( + cmd in build_commands for cmd in sys.argv[1:]) + if building: + # Resolve the compiler even when the objects already exist: this is + # also what makes setuptools use a working compiler for the + # extension itself. + cc = find_build_tool("CC", "cc") if ( len(found_objects) < len(obj_search_path) - and sys.platform != "win32" + and building and os.path.exists("pacparser.c") - and any(cmd in build_commands for cmd in sys.argv[1:]) ): - build_c_objects() + build_c_objects(cc) found_objects["pacparser.o"] = found_objects.get( "pacparser.o", "pacparser.o") found_objects["libquickjs.a"] = found_objects.get(