diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 00a60e2e..e6154525 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -95,7 +95,7 @@ jobs: PYTHON: ${{ matrix.python-version }} run: | for example_dir in examples/*; do - tox -c $example_dir -e py + tox -c $example_dir -e py -vvvv done - name: Test macOS universal2 @@ -224,3 +224,48 @@ jobs: pip3 install rust_with_cffi/dist/rust_with_cffi*.whl python3 -c "from rust_with_cffi import rust; assert rust.rust_func() == 14" python3 -c "from rust_with_cffi.cffi import lib; assert lib.cffi_func() == 15" + + test-cross: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Setup python + uses: actions/setup-python@v2 + with: + python-version: 3.8 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - name: Install cross + run: cargo install cross + - name: Build package + run: pip install -e . + - name: Build wheel using cross + shell: bash + env: + CARGO: cross + CARGO_BUILD_TARGET: aarch64-unknown-linux-gnu + PYO3_CROSS_LIB_DIR: /opt/python/cp38-cp38/lib + run: | + cd examples/namespace_package + docker build -t cross-pyo3:aarch64-unknown-linux-gnu . + python -m pip install wheel + python setup.py bdist_wheel --plat-name manylinux2014_aarch64 + ls -la dist/ + - uses: uraimo/run-on-arch-action@v2.0.5 + name: Install built wheel + with: + arch: aarch64 + distro: ubuntu20.04 + dockerRunArgs: | + --volume "${PWD}/examples/namespace_package:/io" + install: | + apt-get update + apt-get install -y --no-install-recommends python3 python3-pip + pip3 install -U pip + run: | + pip3 install namespace_package --no-index --find-links /io/dist/ --force-reinstall + python3 -c "from namespace_package import rust; assert rust.rust_func() == 14" + python3 -c "from namespace_package import python; assert python.python_func() == 15" diff --git a/CHANGELOG.md b/CHANGELOG.md index 31e05fae..654fb186 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +### Added +- Add support for cross-compiling using [`cross`](https://github.com/rust-embedded/cross). [#185](https://github.com/PyO3/setuptools-rust/pull/185) + ### Fixed - Fix incompatibility with Python 3.6.0 using default values for NamedTuple classes. [#184](https://github.com/PyO3/setuptools-rust/pull/184) diff --git a/examples/namespace_package/Cross.toml b/examples/namespace_package/Cross.toml new file mode 100644 index 00000000..3b971ba6 --- /dev/null +++ b/examples/namespace_package/Cross.toml @@ -0,0 +1,9 @@ +[target.aarch64-unknown-linux-gnu] +image = "cross-pyo3:aarch64-unknown-linux-gnu" + +[build.env] +passthrough = [ + "RUST_BACKTRACE", + "RUST_LOG", + "PYO3_CROSS_LIB_DIR", +] diff --git a/examples/namespace_package/Dockerfile b/examples/namespace_package/Dockerfile new file mode 100644 index 00000000..0ecd9571 --- /dev/null +++ b/examples/namespace_package/Dockerfile @@ -0,0 +1,13 @@ +FROM quay.io/pypa/manylinux2014_aarch64 AS manylinux + +FROM rustembedded/cross:aarch64-unknown-linux-gnu + +RUN apt-get update && \ + apt-get install -y software-properties-common && \ + add-apt-repository ppa:deadsnakes/ppa && \ + apt-get update && \ + apt-get install -y python3.8 && \ + update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.8 1 + +COPY --from=manylinux /opt/_internal /opt/_internal +COPY --from=manylinux /opt/python /opt/python diff --git a/setuptools_rust/build.py b/setuptools_rust/build.py index 9a078cf3..cf109c46 100644 --- a/setuptools_rust/build.py +++ b/setuptools_rust/build.py @@ -15,7 +15,7 @@ ) from distutils.sysconfig import get_config_var from subprocess import check_output -from typing import List, NamedTuple, Optional, cast +from typing import Dict, List, NamedTuple, Optional, cast from setuptools.command.build_ext import build_ext as CommandBuildExt from setuptools.command.build_ext import get_abi3_suffix @@ -27,6 +27,7 @@ binding_features, get_rust_target_info, get_rust_target_list, + split_platform_and_extension, ) @@ -65,6 +66,7 @@ def initialize_options(self) -> None: self.build_temp = None self.plat_name = None self.target = os.getenv("CARGO_BUILD_TARGET") + self.cargo = os.getenv("CARGO", "cargo") def finalize_options(self) -> None: super().finalize_options() @@ -83,76 +85,6 @@ def finalize_options(self) -> None: ("inplace", "inplace"), ) - def get_target_info(self) -> "_TargetInfo": - assert self.plat_name is not None - - # If we are on a 64-bit machine, but running a 32-bit Python, then - # we'll target a 32-bit Rust build. - # Automatic target detection can be overridden via the CARGO_BUILD_TARGET - # environment variable or --target command line option - if self.plat_name == "win32": - return _TargetInfo.for_triple("i686-pc-windows-msvc") - elif self.plat_name == "win-amd64": - return _TargetInfo.for_triple("x86_64-pc-windows-msvc") - elif self.plat_name.startswith("macosx-") and platform.machine() == "x86_64": - # x86_64 or arm64 macOS targeting x86_64 - return _TargetInfo.for_triple("x86_64-apple-darwin") - - cross_compile_info = self.get_nix_cross_compile_info() - if cross_compile_info is not None: - target_info = cross_compile_info.to_target_info() - if target_info is not None: - if self.target is not None: - if not target_info.is_compatible_with(self.target): - self.warn( - f"Forced Rust target `{self.target}` is not " - f"compatible with deduced Rust target " - f"`{target_info.triple}` - the built package may " - f"not import successfully once installed." - ) - else: - return target_info - - if self.target: - return _TargetInfo( - self.target, cross_compile_info.cross_lib, None, None - ) - - raise DistutilsPlatformError( - "Don't know the correct rust target for system type %s. Please " - "set the CARGO_BUILD_TARGET environment variable." - % cross_compile_info.host_type - ) - - # FIXME! - return _TargetInfo.for_triple(self.target) # type: ignore[arg-type] - - def get_nix_cross_compile_info(self) -> Optional["_CrossCompileInfo"]: - # See https://github.com/PyO3/setuptools-rust/issues/138 - # This is to support cross compiling on *NIX, where plat_name isn't - # necessarily the same as the system we are running on. *NIX systems - # have more detailed information available in sysconfig. We need that - # because plat_name doesn't give us information on e.g., glibc vs musl. - host_type = sysconfig.get_config_var("HOST_GNU_TYPE") - build_type = sysconfig.get_config_var("BUILD_GNU_TYPE") - - if not host_type or host_type == build_type: - # not *NIX, or not cross compiling - return None - - stdlib = sysconfig.get_path("stdlib") - assert stdlib is not None - cross_lib = os.path.dirname(stdlib) - - bldshared = sysconfig.get_config_var("BLDSHARED") - if not bldshared: - linker = None - linker_args = None - else: - [linker, linker_args] = bldshared.split(maxsplit=1) - - return _CrossCompileInfo(host_type, cross_lib, linker, linker_args) - def run_for_extension(self, ext: RustExtension) -> None: assert self.plat_name is not None @@ -171,146 +103,97 @@ def run_for_extension(self, ext: RustExtension) -> None: create_universal2_binary(fat_dylib_path, [arm64_dylib, x86_64_dylib]) dylib_paths.append(_BuiltModule(target_fname, fat_dylib_path)) else: - dylib_paths = self.build_extension(ext) + dylib_paths = self.build_extension(ext, self.target) self.install_extension(ext, dylib_paths) def build_extension( - self, ext: RustExtension, target_triple: Optional[str] = None + self, ext: RustExtension, forced_target_triple: Optional[str] ) -> List["_BuiltModule"]: - executable = ext.binding == Binding.Exec - - if target_triple is None: - target_info = self.get_target_info() - else: - target_info = _TargetInfo.for_triple(target_triple) - rust_target_info = get_rust_target_info(target_info.triple) - - # Make sure that if pythonXX-sys is used, it builds against the current - # executing python interpreter. - bindir = os.path.dirname(sys.executable) - - env = os.environ.copy() - env.update( - { - # disables rust's pkg-config seeking for specified packages, - # which causes pythonXX-sys to fall back to detecting the - # interpreter from the path. - "PATH": os.path.join(bindir, os.environ.get("PATH", "")), - "PYTHON_SYS_EXECUTABLE": os.environ.get( - "PYTHON_SYS_EXECUTABLE", sys.executable - ), - "PYO3_PYTHON": os.environ.get("PYO3_PYTHON", sys.executable), - } - ) - if target_info.cross_lib: - env.setdefault("PYO3_CROSS_LIB_DIR", target_info.cross_lib) + target_info = self._detect_rust_target(forced_target_triple) + if target_info is not None: + target_triple = target_info.triple + cross_lib = target_info.cross_lib + linker = target_info.linker + # We're ignoring target_info.linker_args for now because we're not + # sure if they will always do the right thing. Might help with some + # of the OS-specific logic if it does. - rustflags = "" + else: + target_triple = None + cross_lib = None + linker = None - target_args = [] - if target_info.triple is not None: - target_args = ["--target", target_info.triple] + rust_target_info = get_rust_target_info(target_triple) - # Find where to put the temporary build files created by `cargo` - metadata_command = [ - "cargo", - "metadata", - "--manifest-path", - ext.path, - "--format-version", - "1", - ] - metadata = json.loads(check_output(metadata_command)) - target_dir = metadata["target_directory"] + env = _prepare_build_environment(cross_lib) if not os.path.exists(ext.path): raise DistutilsFileError( f"can't find Rust extension project file: {ext.path}" ) - features = { - *ext.features, - *binding_features(ext, py_limited_api=self._py_limited_api()), - } - - debug_build = ext.debug if ext.debug is not None else self.inplace - debug_build = self.debug if self.debug is not None else debug_build - if self.release: - debug_build = False + # Find where to put the temporary build files created by `cargo` + target_dir = _base_cargo_target_dir(ext) + if target_triple is not None: + target_dir = os.path.join(target_dir, target_triple) quiet = self.qbuild or ext.quiet + debug = self._is_debug_build(ext) + cargo_args = self._cargo_args( + ext=ext, target_triple=target_triple, release=not debug, quiet=quiet + ) - # build cargo command - feature_args = ["--features", " ".join(features)] if features else [] - - if executable: - args = ( - ["cargo", "build", "--manifest-path", ext.path] - + feature_args - + target_args - + list(ext.args or []) - ) - if not debug_build: - args.append("--release") - if quiet: - args.append("-q") - elif self.verbose: - # cargo only have -vv - verbose_level = "v" * min(self.verbose, 2) - args.append(f"-{verbose_level}") + if ext._uses_exec_binding(): + command = [self.cargo, "build", "--manifest-path", ext.path, *cargo_args] else: - args = ( - ["cargo", "rustc", "--lib", "--manifest-path", ext.path] - + feature_args - + target_args - + list(ext.args or []) - ) - if not debug_build: - args.append("--release") - if quiet: - args.append("-q") - elif self.verbose: - # cargo only have -vv - verbose_level = "v" * min(self.verbose, 2) - args.append(f"-{verbose_level}") - - args.extend(["--", "--crate-type", "cdylib"]) - args.extend(ext.rustc_flags or []) - - if target_info.linker is not None: - args.extend(["-C", "linker=" + target_info.linker]) - # We're ignoring target_info.linker_args for now because we're not - # sure if they will always do the right thing. Might help with some - # of the OS-specific logic below if it does. + rustc_args = [ + "--crate-type", + "cdylib", + ] - # OSX requires special linker argument + if ext.rustc_flags is not None: + rustc_args.extend(ext.rustc_flags) + + if linker is not None: + rustc_args.extend(["-C", "linker=" + linker]) + + # OSX requires special linker arguments if sys.platform == "darwin": - args.extend( - ["-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup"] + ext_basename = os.path.basename(self.get_dylib_ext_path(ext, ext.name)) + rustc_args.extend( + [ + "-C", + f"link-args=-undefined dynamic_lookup -Wl,-install_name,@rpath/{ext_basename}", + ] ) + + if ext.native: + rustc_args.extend(["-C", "target-cpu=native"]) + # Tell musl targets not to statically link libc. See # https://github.com/rust-lang/rust/issues/59302 for details. if b'target_env="musl"' in rust_target_info: - rustflags += " -C target-feature=-crt-static" + rustc_args.extend(["-C", "target-feature=-crt-static"]) + + command = [ + self.cargo, + "rustc", + "--lib", + "--manifest-path", + ext.path, + *cargo_args, + "--", + *rustc_args, + ] if not quiet: - print(" ".join(args), file=sys.stderr) - - if ext.native: - rustflags += " -C target-cpu=native" - - if not executable and sys.platform == "darwin": - ext_basename = os.path.basename(self.get_dylib_ext_path(ext, ext.name)) - rustflags += f" -C link-args=-Wl,-install_name,@rpath/{ext_basename}" - - if rustflags: - env["RUSTFLAGS"] = (env.get("RUSTFLAGS", "") + " " + rustflags).strip() + print(" ".join(command), file=sys.stderr) # Execute cargo try: - output = subprocess.check_output(args, env=env, encoding="latin-1") + output = subprocess.check_output(command, env=env, encoding="latin-1") except subprocess.CalledProcessError as e: raise CompileError(f"cargo failed with code: {e.returncode}\n{e.output}") @@ -326,16 +209,11 @@ def build_extension( # Find the shared library that cargo hopefully produced and copy # it into the build directory as if it were produced by build_ext. - if debug_build: - suffix = "debug" - else: - suffix = "release" - # location of cargo compiled files - artifactsdir = os.path.join(target_dir, target_info.triple or "", suffix) + artifacts_dir = os.path.join(target_dir, "debug" if debug else "release") dylib_paths = [] - if executable: + if ext._uses_exec_binding(): for name, dest in ext.target.items(): if not name: name = dest.split(".")[-1] @@ -343,13 +221,13 @@ def build_extension( if exe is not None: name += exe - path = os.path.join(artifactsdir, name) + path = os.path.join(artifacts_dir, name) if os.access(path, os.X_OK): dylib_paths.append(_BuiltModule(dest, path)) else: raise DistutilsExecError( "Rust build failed; " - f"unable to find executable '{name}' in '{artifactsdir}'" + f"unable to find executable '{name}' in '{artifacts_dir}'" ) else: if sys.platform == "win32" or sys.platform == "cygwin": @@ -365,19 +243,18 @@ def build_extension( dylib_paths.append( _BuiltModule( ext.name, - next(glob.iglob(os.path.join(artifactsdir, wildcard_so))), + next(glob.iglob(os.path.join(artifacts_dir, wildcard_so))), ) ) except StopIteration: raise DistutilsExecError( - f"Rust build failed; unable to find any {wildcard_so} in {artifactsdir}" + f"Rust build failed; unable to find any {wildcard_so} in {artifacts_dir}" ) return dylib_paths def install_extension( self, ext: RustExtension, dylib_paths: List["_BuiltModule"] ) -> None: - executable = ext.binding == Binding.Exec debug_build = ext.debug if ext.debug is not None else self.inplace debug_build = self.debug if self.debug is not None else debug_build if self.release: @@ -394,15 +271,15 @@ def install_extension( os.path.splitext(os.path.basename(dylib_path)[3:])[0] ) - if executable: + if ext._uses_exec_binding(): ext_path = build_ext.get_ext_fullpath(module_name) - # remove .so extension - ext_path, _ = os.path.splitext(ext_path) - # remove python3 extension (i.e. cpython-36m) - ext_path, _ = os.path.splitext(ext_path) + # remove extensions + ext_path, _, _ = split_platform_and_extension(ext_path) # Add expected extension - ext_path += sysconfig.get_config_var("EXE") + exe = sysconfig.get_config_var("EXE") + if exe is not None: + ext_path += exe os.makedirs(os.path.dirname(ext_path), exist_ok=True) ext.install_script(module_name.split(".")[-1], ext_path) @@ -434,9 +311,10 @@ def install_extension( os.chmod(ext_path, mode) def get_dylib_ext_path(self, ext: RustExtension, target_fname: str) -> str: + assert self.plat_name is not None build_ext = cast(CommandBuildExt, self.get_finalized_command("build_ext")) - filename: str = build_ext.get_ext_fullpath(target_fname) + ext_path: str = build_ext.get_ext_fullpath(target_fname) if (ext.py_limited_api == "auto" and self._py_limited_api()) or ( ext.py_limited_api @@ -445,9 +323,34 @@ def get_dylib_ext_path(self, ext: RustExtension, target_fname: str) -> str: if abi3_suffix is not None: so_ext = get_config_var("EXT_SUFFIX") assert isinstance(so_ext, str) - filename = filename[: -len(so_ext)] + get_abi3_suffix() - - return filename + ext_path = ext_path[: -len(so_ext)] + get_abi3_suffix() + + if ".abi3." in ext_path: + return ext_path + # Examples: linux_x86_64, linux_i686, manylinux2014_aarch64, manylinux_2_24_armv7l + plat_name = self.plat_name.lower().replace("-", "_").replace(".", "_") + if not plat_name.startswith(("linux", "manylinux")): + return ext_path + + arch_parts = [] + arch_found = False + for item in plat_name.split("_"): + if item.startswith(("linux", "manylinux")): + continue + if item.isdigit() and not arch_found: + # manylinux_2_24_armv7l arch should be armv7l + continue + arch_found = True + arch_parts.append(item) + target_arch = "_".join(arch_parts) + host_platform = sysconfig.get_platform() + host_arch = host_platform.rsplit("-", 1)[1] + # Remove incorrect platform tag if we are cross compiling + if target_arch and host_arch != target_arch: + ext_path, _, extension = split_platform_and_extension(ext_path) + # rust.so, removed platform tag + ext_path += extension + return ext_path def _py_limited_api(self) -> PyLimitedApi: bdist_wheel = self.distribution.get_command_obj("bdist_wheel", create=False) @@ -462,6 +365,112 @@ def _py_limited_api(self) -> PyLimitedApi: bdist_wheel_command.ensure_finalized() return cast(PyLimitedApi, bdist_wheel_command.py_limited_api) + def _detect_rust_target( + self, forced_target_triple: Optional[str] + ) -> Optional["_TargetInfo"]: + cross_compile_info = detect_unix_cross_compile_info() + if cross_compile_info is not None: + cross_target_info = cross_compile_info.to_target_info() + if forced_target_triple is not None: + if ( + cross_target_info is not None + and not cross_target_info.is_compatible_with(forced_target_triple) + ): + self.warn( + f"Forced Rust target `{forced_target_triple}` is not " + f"compatible with deduced Rust target " + f"`{cross_target_info.triple}` - the built package " + f" may not import successfully once installed." + ) + + # Forcing the target in a cross-compile environment; use + # the cross-compile information in combination with the + # forced target + return _TargetInfo( + forced_target_triple, + cross_compile_info.cross_lib, + cross_compile_info.linker, + cross_compile_info.linker_args, + ) + elif cross_target_info is not None: + return cross_target_info + else: + raise DistutilsPlatformError( + "Don't know the correct rust target for system type " + f"{cross_compile_info.host_type}. Please set the " + "CARGO_BUILD_TARGET environment variable." + ) + + elif forced_target_triple is not None: + return _TargetInfo.for_triple(forced_target_triple) + + else: + # Automatic target detection can be overridden via the CARGO_BUILD_TARGET + # environment variable or --target command line option + return self._detect_local_rust_target() + + def _detect_local_rust_target(self) -> Optional["_TargetInfo"]: + """Attempts to infer the correct Rust target from build environment for + some edge cases.""" + assert self.plat_name is not None + + # If we are on a 64-bit machine, but running a 32-bit Python, then + # we'll target a 32-bit Rust build. + if self.plat_name == "win32": + return _TargetInfo.for_triple("i686-pc-windows-msvc") + elif self.plat_name == "win-amd64": + return _TargetInfo.for_triple("x86_64-pc-windows-msvc") + elif self.plat_name.startswith("macosx-") and platform.machine() == "x86_64": + # x86_64 or arm64 macOS targeting x86_64 + return _TargetInfo.for_triple("x86_64-apple-darwin") + else: + return None + + def _is_debug_build(self, ext: RustExtension) -> bool: + if self.release: + return False + elif self.debug is not None: + return self.debug + elif ext.debug is not None: + return ext.debug + else: + return bool(self.inplace) + + def _cargo_args( + self, + ext: RustExtension, + target_triple: Optional[str], + release: bool, + quiet: bool, + ) -> List[str]: + args = [] + if target_triple is not None: + args.extend(["--target", target_triple]) + + if release: + args.append("--release") + + if quiet: + args.append("-q") + + elif self.verbose: + # cargo only have -vv + verbose_level = "v" * min(self.verbose, 2) + args.append(f"-{verbose_level}") + + features = { + *ext.features, + *binding_features(ext, py_limited_api=self._py_limited_api()), + } + + if features: + args.extend(["--features", " ".join(features)]) + + if ext.args is not None: + args.extend(ext.args) + + return args + def create_universal2_binary(output_path: str, input_paths: List[str]) -> None: # Try lipo first @@ -552,6 +561,33 @@ def to_target_info(self) -> Optional[_TargetInfo]: return None +def detect_unix_cross_compile_info() -> Optional["_CrossCompileInfo"]: + # See https://github.com/PyO3/setuptools-rust/issues/138 + # This is to support cross compiling on *NIX, where plat_name isn't + # necessarily the same as the system we are running on. *NIX systems + # have more detailed information available in sysconfig. We need that + # because plat_name doesn't give us information on e.g., glibc vs musl. + host_type = sysconfig.get_config_var("HOST_GNU_TYPE") + build_type = sysconfig.get_config_var("BUILD_GNU_TYPE") + + if not host_type or host_type == build_type: + # not *NIX, or not cross compiling + return None + + stdlib = sysconfig.get_path("stdlib") + assert stdlib is not None + cross_lib = os.path.dirname(stdlib) + + bldshared = sysconfig.get_config_var("BLDSHARED") + if not bldshared: + linker = None + linker_args = None + else: + [linker, linker_args] = bldshared.split(maxsplit=1) + + return _CrossCompileInfo(host_type, cross_lib, linker, linker_args) + + def _replace_vendor_with_unknown(target: str) -> Optional[str]: """Replaces vendor in the target triple with unknown. @@ -562,3 +598,52 @@ def _replace_vendor_with_unknown(target: str) -> Optional[str]: return None components[1] = "unknown" return "-".join(components) + + +def _prepare_build_environment(cross_lib: Optional[str]) -> Dict[str, str]: + """Prepares environment variables to use when executing cargo build.""" + + # Make sure that if pythonXX-sys is used, it builds against the current + # executing python interpreter. + bindir = os.path.dirname(sys.executable) + + env = os.environ.copy() + env.update( + { + # disables rust's pkg-config seeking for specified packages, + # which causes pythonXX-sys to fall back to detecting the + # interpreter from the path. + "PATH": os.path.join(bindir, os.environ.get("PATH", "")), + "PYTHON_SYS_EXECUTABLE": os.environ.get( + "PYTHON_SYS_EXECUTABLE", sys.executable + ), + "PYO3_PYTHON": os.environ.get("PYO3_PYTHON", sys.executable), + } + ) + + if cross_lib: + env.setdefault("PYO3_CROSS_LIB_DIR", cross_lib) + + return env + + +def _base_cargo_target_dir(ext: RustExtension) -> str: + """Returns the root target directory cargo will use. + + If --target is passed to cargo in the command line, the target directory + will have the target appended as a child. + """ + metadata_command = [ + "cargo", + "metadata", + "--manifest-path", + ext.path, + "--format-version", + "1", + ] + metadata = json.loads(check_output(metadata_command)) + target_directory = metadata["target_directory"] + assert isinstance( + target_directory, str + ), "expected cargo metadata to return a string target directory" + return target_directory diff --git a/setuptools_rust/extension.py b/setuptools_rust/extension.py index 2ca7ba54..97645fe0 100644 --- a/setuptools_rust/extension.py +++ b/setuptools_rust/extension.py @@ -191,6 +191,9 @@ def install_script(self, module_name: str, exe_path: str) -> None: with open(file, "w") as f: f.write(TMPL.format(executable=repr(executable))) + def _uses_exec_binding(self) -> bool: + return self.binding == Binding.Exec + TMPL = """ import os diff --git a/setuptools_rust/setuptools_ext.py b/setuptools_rust/setuptools_ext.py index 2f536b37..7209ee44 100644 --- a/setuptools_rust/setuptools_ext.py +++ b/setuptools_rust/setuptools_ext.py @@ -124,9 +124,11 @@ def run(self) -> None: log.info("running build_rust") build_rust = self.get_finalized_command("build_rust") build_rust.inplace = self.inplace - build_rust.plat_name = self.plat_name build_rust.target = self.target build_rust.verbose = self.verbose + options = self.distribution.get_cmdline_options().get("bdist_wheel", {}) + plat_name = options.get("plat-name") or self.plat_name + build_rust.plat_name = plat_name build_rust.run() build_ext_base_class.run(self) diff --git a/setuptools_rust/utils.py b/setuptools_rust/utils.py index 2d0603bd..3d418250 100644 --- a/setuptools_rust/utils.py +++ b/setuptools_rust/utils.py @@ -1,6 +1,7 @@ +import os import subprocess from distutils.errors import DistutilsPlatformError -from typing import List, Optional, Set, Union +from typing import List, Optional, Set, Tuple, Union from semantic_version import Version from typing_extensions import Literal @@ -58,3 +59,17 @@ def get_rust_target_list() -> List[str]: ) _rust_target_list = output.splitlines() return _rust_target_list + + +def split_platform_and_extension(ext_path: str) -> Tuple[str, str, str]: + """Splits an extension path into a tuple (ext_path, plat_tag, extension). + + >>> split_platform_and_extension("foo/bar.platform.so") + ('foo/bar', '.platform', '.so') + """ + + # rust.cpython-38-x86_64-linux-gnu.so to (rust.cpython-38-x86_64-linux-gnu, .so) + ext_path, extension = os.path.splitext(ext_path) + # rust.cpython-38-x86_64-linux-gnu to (rust, .cpython-38-x86_64-linux-gnu) + ext_path, platform_tag = os.path.splitext(ext_path) + return (ext_path, platform_tag, extension)