diff --git a/src/pymod/setup.py b/src/pymod/setup.py index 9939338..2d64ffb 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,15 +183,48 @@ def run(self): shutil.rmtree("quickjs") -def build_c_objects(): +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. + """ + 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])) + 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(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 = shlex.split(sysconfig.get_config_var("CC") or "cc") - ar = shlex.split(sysconfig.get_config_var("AR") or "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( @@ -225,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(