From b0a28579bf121e134b17050e38ae084ef0cf7a4d Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Fri, 1 Jul 2022 22:00:27 -0700 Subject: [PATCH] Fix pywasmcross when pyodide_build is installed out of tree (#2824) Symlinking cc to pywasmcross.py only works if pywasmcross.py has execute permissions. When we install the package with pip, it will not set execute permissions on pywasmcross.py. It does set execute flags on entrypoints. Thus, define an entrypoint called _pywasmcross which calls pywasmcross.main. If a script called _pywasmcross exists, we are using an out-of-tree install so symlink cc to _pywasmcross. Otherwise, we should be in tree and pywasmcross.py should have the execute flag set, so symlink cc to pywasmcross.py. On the other side, if __main__.__file__ is in a folder named pyodide_build or bin we are being invoked normally, otherwise we are being invoked via a symlink. --- pyodide-build/pyodide_build/pywasmcross.py | 52 ++++++++++++++-------- pyodide-build/setup.py | 8 +++- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/pyodide-build/pyodide_build/pywasmcross.py b/pyodide-build/pyodide_build/pywasmcross.py index 0beae6915..7d9a224e6 100755 --- a/pyodide-build/pyodide_build/pywasmcross.py +++ b/pyodide-build/pyodide_build/pywasmcross.py @@ -12,33 +12,45 @@ import json import os import sys from pathlib import Path, PurePosixPath +from typing import Any -IS_MAIN = __name__ == "__main__" -if IS_MAIN: +from __main__ import __file__ as INVOKED_PATH_STR + +INVOKED_PATH = Path(INVOKED_PATH_STR) + +SYMLINKS = {"cc", "c++", "ld", "ar", "gcc", "gfortran", "cargo"} +IS_COMPILER_INVOCATION = INVOKED_PATH.name in SYMLINKS + +if IS_COMPILER_INVOCATION: # If possible load from environment variable, if necessary load from disk. if "PYWASMCROSS_ARGS" in os.environ: PYWASMCROSS_ARGS = json.loads(os.environ["PYWASMCROSS_ARGS"]) - else: - with open(Path(__file__).parent / "pywasmcross_env.json") as f: + try: + with open(INVOKED_PATH.parent / "pywasmcross_env.json") as f: PYWASMCROSS_ARGS = json.load(f) + except FileNotFoundError: + raise RuntimeError( + "Invalid invocation: can't find PYWASMCROSS_ARGS." + f" Invoked from {INVOKED_PATH}." + ) - # restore __name__ so that relative imports work as we expect - __name__ = PYWASMCROSS_ARGS.pop("orig__name__") sys.path = PYWASMCROSS_ARGS.pop("PYTHONPATH") os.environ["PATH"] = PYWASMCROSS_ARGS.pop("PATH") + # restore __name__ so that relative imports work as we expect + __name__ = PYWASMCROSS_ARGS.pop("orig__name__") + import re +import shutil import subprocess from collections import namedtuple from contextlib import contextmanager from tempfile import TemporaryDirectory -from typing import Any, Iterator, Literal, MutableMapping, NoReturn +from typing import Iterator, Literal, MutableMapping, NoReturn from pyodide_build import common from pyodide_build._f2c_fixes import fix_f2c_input, fix_f2c_output, scipy_fixes -symlinks = {"cc", "c++", "ld", "ar", "gcc", "gfortran", "cargo"} - def symlink_dir() -> Path: return Path(common.get_make_flag("TOOLSDIR")) / "symlinks" @@ -68,13 +80,17 @@ def make_command_wrapper_symlinks( exist. """ exec_path = Path(__file__).resolve() - for symlink in symlinks: + for symlink in SYMLINKS: symlink_path = symlink_dir / symlink if os.path.lexists(symlink_path) and not symlink_path.exists(): # remove broken symlink so it can be re-created symlink_path.unlink() try: - symlink_path.symlink_to(exec_path) + pywasmcross_exe = shutil.which("_pywasmcross") + if pywasmcross_exe: + symlink_path.symlink_to(pywasmcross_exe) + else: + symlink_path.symlink_to(exec_path) except FileExistsError: pass if symlink == "c++": @@ -609,13 +625,13 @@ def environment_substitute_args( return subbed_args -if IS_MAIN: - REPLAY_ARGS = ReplayArgs(**PYWASMCROSS_ARGS) - +def compiler_main(): + replay_args = ReplayArgs(**PYWASMCROSS_ARGS) basename = Path(sys.argv[0]).name args = list(sys.argv) args[0] = basename - if basename in symlinks: - sys.exit(handle_command(args, REPLAY_ARGS)) - else: - raise Exception(f"Unexpected invocation '{basename}'") + sys.exit(handle_command(args, replay_args)) + + +if IS_COMPILER_INVOCATION: + compiler_main() diff --git a/pyodide-build/setup.py b/pyodide-build/setup.py index 25f849ce1..52a7cac56 100644 --- a/pyodide-build/setup.py +++ b/pyodide-build/setup.py @@ -2,4 +2,10 @@ import setuptools if __name__ == "__main__": - setuptools.setup() + setuptools.setup( + entry_points={ + "console_scripts": [ + "_pywasmcross = pyodide_build.pywasmcross:compiler_main", + ] + } + )