def make_parser(parser): basename = Path(sys.argv[0]).name if basename in symlinks: # skip parsing of all arguments parser._actions = [] else: parser.description = ( "Cross compile a Python distutils package. " "Run from the root directory of the package's source") parser.add_argument( "--cflags", type=str, nargs="?", default=common.get_make_flag("SIDE_MODULE_CFLAGS"), help="Extra compiling flags", action=EnvironmentRewritingArgument, ) parser.add_argument( "--cxxflags", type=str, nargs="?", default=common.get_make_flag("SIDE_MODULE_CXXFLAGS"), help="Extra C++ specific compiling flags", action=EnvironmentRewritingArgument, ) parser.add_argument( "--ldflags", type=str, nargs="?", default=common.get_make_flag("SIDE_MODULE_LDFLAGS"), help="Extra linking flags", action=EnvironmentRewritingArgument, ) parser.add_argument( "--target", type=str, nargs="?", default=common.get_make_flag("TARGETPYTHONROOT"), help="The path to the target Python installation", ) parser.add_argument( "--install-dir", type=str, nargs="?", default="", help= ("Directory for installing built host packages. Defaults to setup.py " "default. Set to 'skip' to skip installation. Installation is " "needed if you want to build other packages that depend on this one." ), ) parser.add_argument( "--replace-libs", type=str, nargs="?", default="", help="Libraries to replace in final link", action=EnvironmentRewritingArgument, ) return parser
def test_wheel_paths(): from pathlib import Path old_version = "cp38" PYMAJOR = int(get_make_flag("PYMAJOR")) PYMINOR = int(get_make_flag("PYMINOR")) current_version = f"cp{PYMAJOR}{PYMINOR}" future_version = f"cp{PYMAJOR}{PYMINOR + 1}" strings = [] for interp in [ old_version, current_version, future_version, "py3", "py2", "py2.py3", ]: for abi in [interp, "abi3", "none"]: for arch in ["emscripten_wasm32", "linux_x86_64", "any"]: strings.append(f"wrapt-1.13.3-{interp}-{abi}-{arch}.whl") paths = [Path(x) for x in strings] assert [x.stem.split("-", 2)[-1] for x in find_matching_wheels(paths)] == [ f"{current_version}-{current_version}-emscripten_wasm32", f"{current_version}-abi3-emscripten_wasm32", f"{current_version}-none-emscripten_wasm32", f"{old_version}-abi3-emscripten_wasm32", "py3-none-emscripten_wasm32", "py2.py3-none-emscripten_wasm32", "py3-none-any", "py2.py3-none-any", ]
def capture_command(command: str, args: list[str]) -> int: """ This is called when this script is called through a symlink that looks like a compiler or linker. It writes the arguments to the build.log, and then delegates to the real native compiler or linker (unless it decides to skip host compilation). Returns ------- The exit code of the real native compiler invocation """ TOOLSDIR = Path(common.get_make_flag("TOOLSDIR")) # Remove the symlink compiler from the PATH, so we can delegate to the # native compiler env = dict(os.environ) path = env["PATH"] while str(TOOLSDIR) + ":" in path: path = path.replace(str(TOOLSDIR) + ":", "") env["PATH"] = path skip_host = "SKIP_HOST" in os.environ # Skip compilations of C/Fortran extensions for the target environment. # We still need to generate the output files for distutils to continue # the build. # TODO: This may need slight tuning for new projects. In particular, # currently ar is not skipped, so a known failure would happen when # we create some object files (that are empty as gcc is skipped), on # which we run the actual ar command. skip = False if (command in ["gcc", "cc", "c++", "gfortran", "ld"] and "-o" in args # do not skip numpy as it is needed as build time # dependency by other packages (e.g. matplotlib) and skip_host): out_idx = args.index("-o") if (out_idx + 1) < len(args): # get the index of the output file path out_idx += 1 with open(args[out_idx], "wb") as fh: fh.write(b"") skip = True with open("build.log", "a") as fd: # TODO: store skip status in the build.log json.dump([command] + args, fd) fd.write("\n") if skip: return 0 compiler_command = [command] if shutil.which("ccache") is not None: # Enable ccache if it's installed compiler_command.insert(0, "ccache") return subprocess.run(compiler_command + args, env=env).returncode
def capture_compile(args): TOOLSDIR = Path(common.get_make_flag("TOOLSDIR")) env = dict(os.environ) make_symlinks(env) env["PATH"] = str(TOOLSDIR) + ":" + os.environ["PATH"] cmd = [sys.executable, "setup.py", "install"] if args.install_dir == "skip": cmd[-1] = "build" elif args.install_dir != "": cmd.extend(["--home", args.install_dir]) result = subprocess.run(cmd, env=env) if result.returncode != 0: build_log_path = Path("build.log") if build_log_path.exists(): build_log_path.unlink() sys.exit(result.returncode)
def make_symlinks(env): """ Makes sure all of the symlinks that make this script look like a compiler exist. """ TOOLSDIR = Path(common.get_make_flag("TOOLSDIR")) exec_path = Path(__file__).resolve() for symlink in symlinks: symlink_path = TOOLSDIR / 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) except FileExistsError: pass if symlink == "c++": var = "CXX" else: var = symlink.upper() env[var] = symlink
def capture_compile(*, host_install_dir: str, skip_host: bool, env: dict[str, str]): TOOLSDIR = Path(common.get_make_flag("TOOLSDIR")) env = dict(env) env["PATH"] = str(TOOLSDIR) + ":" + env["PATH"] capture_make_command_wrapper_symlinks(env) cmd = [sys.executable, "setup.py"] if skip_host: env["SKIP_HOST"] = "1" cmd.append("build") else: assert host_install_dir, "Missing host_install_dir" cmd.extend(["install", "--home", host_install_dir]) result = subprocess.run(cmd, env=env) if result.returncode != 0: build_log_path = Path("build.log") if build_log_path.exists(): build_log_path.unlink() result.check_returncode() clean_out_native_artifacts()
def test_get_make_flag(): assert len(get_make_flag("SIDE_MODULE_LDFLAGS")) > 0 assert len(get_make_flag("SIDE_MODULE_CFLAGS")) > 0 # n.b. right now CXXFLAGS is empty so don't check length here, just check it returns get_make_flag("SIDE_MODULE_CXXFLAGS")
import argparse import importlib.machinery import json import os from pathlib import Path, PurePosixPath import re import subprocess import shutil import sys # absolute import is necessary as this file will be symlinked # under tools from pyodide_build import common from pyodide_build._f2c_fixes import fix_f2c_clapack_calls TOOLSDIR = Path(common.get_make_flag("TOOLSDIR")) symlinks = set(["cc", "c++", "ld", "ar", "gcc", "gfortran"]) class EnvironmentRewritingArgument(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): for e_name, e_value in os.environ.items(): values = values.replace(f"$({e_name})", e_value) setattr(namespace, self.dest, values) def collect_args(basename): """ This is called when this script is called through a symlink that looks like a compiler or linker.
def symlink_dir(): return Path(common.get_make_flag("TOOLSDIR")) / "symlinks"