#[cfg(feature = "zig")]
use crate::PlatformTag;
use crate::target::{RUST_1_64_0, RUST_1_93_0, rustc_macosx_target_version};
use crate::{BridgeModel, BuildContext, PythonInterpreter, Target};
use anyhow::{Context, Result, anyhow, bail};
use cargo_metadata::CrateType;
use fat_macho::FatWriter;
use fs_err::{self as fs, File};
use normpath::PathExt;
use std::collections::{HashMap, HashSet};
use std::env;
use std::io::BufReader;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::str;
use tracing::{debug, instrument, trace};
const PYO3_ABI3_NO_PYTHON_VERSION: (u64, u64, u64) = (0, 16, 4);
pub(crate) const LIB_CRATE_TYPES: [CrateType; 4] = [
CrateType::Lib,
CrateType::DyLib,
CrateType::RLib,
CrateType::StaticLib,
];
#[derive(Debug, Clone)]
pub struct CompileTarget {
pub target: cargo_metadata::Target,
pub bridge_model: BridgeModel,
}
#[derive(Debug, Clone)]
pub struct ThinArtifact {
pub arch: String,
pub path: PathBuf,
pub linked_paths: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct BuildArtifact {
pub path: PathBuf,
pub import_lib_path: Option<PathBuf>,
pub debuginfo_path: Option<PathBuf>,
pub linked_paths: Vec<String>,
pub thin_artifacts: Vec<ThinArtifact>,
}
#[derive(Debug, Clone)]
pub struct CompileResult {
pub artifacts: Vec<HashMap<CrateType, BuildArtifact>>,
pub out_dirs: HashMap<String, PathBuf>,
}
pub fn compile(
context: &BuildContext,
python_interpreter: Option<&PythonInterpreter>,
targets: &[CompileTarget],
) -> Result<CompileResult> {
if context.project.universal2 {
compile_universal2(context, python_interpreter, targets)
} else {
compile_targets(context, python_interpreter, targets)
}
}
pub(crate) fn filter_cargo_targets(
cargo_metadata: &cargo_metadata::Metadata,
bridge: BridgeModel,
config_targets: Option<&[crate::pyproject_toml::CargoTarget]>,
) -> Result<Vec<CompileTarget>> {
let root_pkg = cargo_metadata
.root_package()
.ok_or_else(|| anyhow!("No root package found in cargo metadata"))?;
let resolved_features: Vec<String> = cargo_metadata
.resolve
.as_ref()
.and_then(|resolve| resolve.nodes.iter().find(|&node| node.id == root_pkg.id))
.map(|node| node.features.iter().map(|f| f.to_string()).collect())
.unwrap_or_default();
let mut targets: Vec<_> = root_pkg
.targets
.iter()
.filter(|&target| match bridge {
BridgeModel::Bin(_) => {
let is_bin = target.is_bin();
if target.required_features.is_empty() {
is_bin
} else {
is_bin
&& target
.required_features
.iter()
.all(|f| resolved_features.contains(f))
}
}
_ => target.crate_types.contains(&CrateType::CDyLib),
})
.map(|target| CompileTarget {
target: target.clone(),
bridge_model: bridge.clone(),
})
.collect();
if targets.is_empty() && !bridge.is_bin() {
let lib_target = root_pkg.targets.iter().find(|target| {
target
.crate_types
.iter()
.any(|crate_type| LIB_CRATE_TYPES.contains(crate_type))
});
if let Some(target) = lib_target {
targets.push(CompileTarget {
target: target.clone(),
bridge_model: bridge,
});
}
}
if let Some(config_targets) = config_targets {
targets.retain(|CompileTarget { target, .. }| {
config_targets.iter().any(|config_target| {
let name_eq = config_target.name == target.name;
match &config_target.kind {
Some(kind) => name_eq && target.crate_types.contains(&CrateType::from(*kind)),
None => name_eq,
}
})
});
if targets.is_empty() {
bail!(
"No Cargo targets matched by `package.metadata.maturin.targets`, please check your `Cargo.toml`"
);
} else {
let target_names = targets
.iter()
.map(|CompileTarget { target, .. }| target.name.as_str())
.collect::<Vec<_>>();
eprintln!(
"🎯 Found {} Cargo targets in `Cargo.toml`: {}",
targets.len(),
target_names.join(", ")
);
}
}
Ok(targets)
}
fn compile_universal2(
context: &BuildContext,
python_interpreter: Option<&PythonInterpreter>,
targets: &[CompileTarget],
) -> Result<CompileResult> {
let mut aarch64_context = context.clone();
aarch64_context.project.target = Target::from_resolved_target_triple("aarch64-apple-darwin")?;
let aarch64_result = compile_targets(&aarch64_context, python_interpreter, targets)
.context("Failed to build a aarch64 library through cargo")?;
let mut x86_64_context = context.clone();
x86_64_context.project.target = Target::from_resolved_target_triple("x86_64-apple-darwin")?;
let x86_64_result = compile_targets(&x86_64_context, python_interpreter, targets)
.context("Failed to build a x86_64 library through cargo")?;
let mut universal_artifacts = Vec::with_capacity(targets.len());
for (bridge_model, (aarch64_artifact, x86_64_artifact)) in
targets.iter().map(|target| &target.bridge_model).zip(
aarch64_result
.artifacts
.iter()
.zip(&x86_64_result.artifacts),
)
{
let build_type = if bridge_model.is_bin() {
CrateType::Bin
} else {
CrateType::CDyLib
};
let aarch64_artifact = aarch64_artifact.get(&build_type).cloned().ok_or_else(|| {
if build_type == CrateType::CDyLib {
anyhow!(
"Cargo didn't build an aarch64 cdylib. Did you miss crate-type = [\"cdylib\"] \
in the lib section of your Cargo.toml?",
)
} else {
anyhow!("Cargo didn't build an aarch64 bin.")
}
})?;
let x86_64_artifact = x86_64_artifact.get(&build_type).cloned().ok_or_else(|| {
if build_type == CrateType::CDyLib {
anyhow!(
"Cargo didn't build a x86_64 cdylib. Did you miss crate-type = [\"cdylib\"] \
in the lib section of your Cargo.toml?",
)
} else {
anyhow!("Cargo didn't build a x86_64 bin.")
}
})?;
let output_path = aarch64_artifact
.path
.display()
.to_string()
.replace("aarch64-apple-darwin/", "");
let mut writer = FatWriter::new();
let aarch64_file = fs::read(&aarch64_artifact.path)?;
let x86_64_file = fs::read(&x86_64_artifact.path)?;
writer
.add(aarch64_file)
.map_err(|e| anyhow!("Failed to add aarch64 cdylib: {:?}", e))?;
writer
.add(x86_64_file)
.map_err(|e| anyhow!("Failed to add x86_64 cdylib: {:?}", e))?;
writer
.write_to_file(&output_path)
.map_err(|e| anyhow!("Failed to create universal cdylib: {:?}", e))?;
let mut result = HashMap::new();
let mut seen_paths: HashSet<&str> = HashSet::new();
let mut linked_paths = Vec::new();
for p in aarch64_artifact
.linked_paths
.iter()
.chain(&x86_64_artifact.linked_paths)
{
if seen_paths.insert(p.as_str()) {
linked_paths.push(p.clone());
}
}
let thin_artifacts = vec![
ThinArtifact {
arch: "arm64".to_string(),
path: aarch64_artifact.path.clone(),
linked_paths: aarch64_artifact.linked_paths.clone(),
},
ThinArtifact {
arch: "x86_64".to_string(),
path: x86_64_artifact.path.clone(),
linked_paths: x86_64_artifact.linked_paths.clone(),
},
];
let universal_artifact = BuildArtifact {
path: PathBuf::from(output_path),
import_lib_path: None,
debuginfo_path: None,
linked_paths,
thin_artifacts,
};
result.insert(build_type, universal_artifact);
universal_artifacts.push(result);
}
Ok(CompileResult {
artifacts: universal_artifacts,
out_dirs: x86_64_result.out_dirs,
})
}
fn compile_targets(
context: &BuildContext,
python_interpreter: Option<&PythonInterpreter>,
targets: &[CompileTarget],
) -> Result<CompileResult> {
let mut artifacts = Vec::with_capacity(targets.len());
let mut out_dirs = HashMap::new();
for target in targets {
let build_command = cargo_build_command(context, python_interpreter, target)?;
let (target_artifacts, target_out_dirs) = compile_target(context, build_command)?;
artifacts.push(target_artifacts);
out_dirs.extend(target_out_dirs);
}
Ok(CompileResult {
artifacts,
out_dirs,
})
}
fn cargo_build_command(
context: &BuildContext,
python_interpreter: Option<&PythonInterpreter>,
compile_target: &CompileTarget,
) -> Result<Command> {
use crate::pyproject_toml::{FeatureConditionEnv, FeatureSpec};
let target = &context.project.target;
let user_specified_target = if target.user_specified {
Some(target.target_triple().to_string())
} else {
None
};
let mut cargo_options = context.project.cargo_options.clone();
if let Some(interpreter) = python_interpreter {
let env = FeatureConditionEnv {
major: interpreter.major,
minor: interpreter.minor,
implementation_name: &interpreter.implementation_name,
};
let extra = FeatureSpec::resolve_conditional(&context.project.conditional_features, &env);
if !extra.is_empty() {
debug!(
"Enabling conditional features for Python {} {}.{}: {}",
interpreter.implementation_name,
interpreter.major,
interpreter.minor,
extra.join(", ")
);
for feature in extra {
if !cargo_options.features.contains(&feature) {
cargo_options.features.push(feature);
}
}
}
}
let mut cargo_rustc = cargo_options.into_rustc_options(user_specified_target);
cargo_rustc.message_format = vec!["json-render-diagnostics".to_string()];
if compile_target
.target
.crate_types
.iter()
.any(|crate_type| LIB_CRATE_TYPES.contains(crate_type))
{
if target.rustc_version.semver >= RUST_1_64_0 {
debug!("Setting crate_type to cdylib for Rust >= 1.64.0");
cargo_rustc.crate_type = vec!["cdylib".to_string()];
}
}
let target_triple = target.target_triple();
let manifest_dir = context.project.manifest_path.parent().unwrap();
let mut rustflags = cargo_config2::Config::load_with_cwd(manifest_dir)?
.rustflags(target_triple)?
.unwrap_or_default();
let original_rustflags = rustflags.flags.clone();
if let Some(ref pgo_phase) = context.artifact.pgo_phase {
match pgo_phase {
crate::pgo::PgoPhase::Generate(profdata_dir) => {
rustflags
.flags
.push(format!("-Cprofile-generate={}", profdata_dir.display()));
}
crate::pgo::PgoPhase::Use(profdata_path) => {
rustflags
.flags
.push(format!("-Cprofile-use={}", profdata_path.display()));
}
}
}
let bridge_model = &compile_target.bridge_model;
configure_bin_lib_flags(
&mut cargo_rustc,
&mut rustflags,
compile_target,
bridge_model,
target,
);
configure_platform_linker_args(
&mut cargo_rustc,
&mut rustflags,
target,
bridge_model,
&context.project.module_name,
python_interpreter,
#[cfg(feature = "zig")]
context.python.zig,
#[cfg(feature = "zig")]
&context.project.target_dir,
);
if context.artifact.strip {
cargo_rustc
.args
.extend(["-C".to_string(), "strip=symbols".to_string()]);
}
configure_debuginfo_flags(&mut rustflags, target, context.artifact.include_debuginfo)?;
let mut build_command = create_build_command(context, cargo_rustc, target_triple)?;
if !rustflags.flags.is_empty() && rustflags.flags != original_rustflags {
build_command.env("CARGO_ENCODED_RUSTFLAGS", rustflags.encode()?);
}
configure_pyo3_env(
&mut build_command,
context,
python_interpreter,
bridge_model,
target,
target_triple,
)?;
Ok(build_command)
}
fn configure_bin_lib_flags(
cargo_rustc: &mut cargo_options::Rustc,
rustflags: &mut cargo_config2::Flags,
compile_target: &CompileTarget,
bridge_model: &BridgeModel,
target: &Target,
) {
match bridge_model {
BridgeModel::Bin(..) => {
cargo_rustc.bin.push(compile_target.target.name.clone());
}
BridgeModel::Cffi | BridgeModel::UniFfi | BridgeModel::PyO3 { .. } => {
cargo_rustc.lib = true;
if target.is_musl_libc()
&& !rustflags
.flags
.iter()
.any(|f| f == "target-feature=-crt-static")
{
debug!("Setting `-C target-features=-crt-static` for musl dylib");
rustflags.push("-C");
rustflags.push("target-feature=-crt-static");
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn configure_platform_linker_args(
cargo_rustc: &mut cargo_options::Rustc,
rustflags: &mut cargo_config2::Flags,
target: &Target,
bridge_model: &BridgeModel,
module_name: &str,
python_interpreter: Option<&PythonInterpreter>,
#[cfg(feature = "zig")] zig: bool,
#[cfg(feature = "zig")] target_dir: &Path,
) {
if target.is_macos() {
if let BridgeModel::PyO3 { .. } = bridge_model {
configure_macos_pyo3_linker_args(
cargo_rustc,
rustflags,
bridge_model,
module_name,
python_interpreter,
);
}
} else if target.is_emscripten() {
configure_emscripten_args(cargo_rustc, rustflags, target);
}
#[cfg(feature = "zig")]
if zig
&& target.is_windows()
&& !target.is_msvc()
&& matches!(
bridge_model,
BridgeModel::PyO3 { .. } | BridgeModel::Cffi | BridgeModel::UniFfi
)
{
let py_init = format!("PyInit_{module_name}");
let maturin_dir = ensure_target_maturin_dir(target_dir);
let def_path = maturin_dir.join(format!("{module_name}.def"));
let def_contents = format!("LIBRARY {module_name}\nEXPORTS\n {py_init}\n");
if let Err(e) = fs::write(&def_path, def_contents) {
eprintln!("⚠️ Warning: Failed to write .def file for zig windows-gnu workaround: {e}");
} else {
debug!(
"Generated .def file at {} for zig windows-gnu export workaround",
def_path.display()
);
rustflags.push("-C");
rustflags.push(format!("link-arg={}", def_path.display()));
}
}
}
fn configure_macos_pyo3_linker_args(
cargo_rustc: &mut cargo_options::Rustc,
rustflags: &mut cargo_config2::Flags,
bridge_model: &BridgeModel,
module_name: &str,
python_interpreter: Option<&PythonInterpreter>,
) {
let has_undefined = rustflags
.flags
.iter()
.any(|f| f == "link-arg=-undefined" || f == "-Clink-arg=-undefined");
let has_dynamic_lookup = rustflags
.flags
.iter()
.any(|f| f == "link-arg=dynamic_lookup" || f == "-Clink-arg=dynamic_lookup");
if !has_undefined {
rustflags.push("-C");
rustflags.push("link-arg=-undefined");
}
if !has_dynamic_lookup {
rustflags.push("-C");
rustflags.push("link-arg=dynamic_lookup");
}
let so_filename = if bridge_model.is_abi3() {
format!("{module_name}.abi3.so")
} else {
python_interpreter
.expect("missing python interpreter for non-abi3 wheel build")
.get_library_name(module_name)
};
let macos_dylib_install_name = format!("link-args=-Wl,-install_name,@rpath/{so_filename}");
let install_name_args = ["-C".to_string(), macos_dylib_install_name];
debug!(
"Setting macOS install_name via cargo rustc args: {:?}",
install_name_args
);
cargo_rustc.args.extend(install_name_args);
}
fn configure_emscripten_args(
cargo_rustc: &mut cargo_options::Rustc,
rustflags: &mut cargo_config2::Flags,
target: &Target,
) {
if target.rustc_version.semver < RUST_1_93_0
&& !rustflags
.flags
.iter()
.any(|f| f.contains("link-native-libraries"))
{
debug!("Setting `-Z link-native-libraries=no` for Emscripten (rust < 1.93.0)");
rustflags.push("-Z");
rustflags.push("link-native-libraries=no");
}
let mut emscripten_args = Vec::new();
if !cargo_rustc
.args
.iter()
.any(|arg| arg.contains("SIDE_MODULE"))
{
emscripten_args.push("-C".to_string());
emscripten_args.push("link-arg=-sSIDE_MODULE=2".to_string());
}
if !cargo_rustc
.args
.iter()
.any(|arg| arg.contains("WASM_BIGINT"))
{
emscripten_args.push("-C".to_string());
emscripten_args.push("link-arg=-sWASM_BIGINT".to_string());
}
debug!(
"Setting additional linker args for Emscripten: {:?}",
emscripten_args
);
cargo_rustc.args.extend(emscripten_args);
}
fn configure_debuginfo_flags(
rustflags: &mut cargo_config2::Flags,
target: &Target,
include_debuginfo: bool,
) -> Result<()> {
if !include_debuginfo {
return Ok(());
}
let has_split_debuginfo = rustflags
.flags
.iter()
.any(|f| f.contains("split-debuginfo"));
if has_split_debuginfo {
let has_unpacked = rustflags
.flags
.iter()
.any(|f| f.contains("split-debuginfo=unpacked"));
if has_unpacked && target.is_linux() {
bail!(
"split-debuginfo=unpacked is incompatible with --include-debuginfo on Linux \
because debug info is scattered into .dwo files that cannot be included. \
Use `-C split-debuginfo=packed` or `-C split-debuginfo=off` instead."
);
}
} else if target.is_macos() {
debug!("Setting `-C split-debuginfo=packed` for --include-debuginfo on macOS");
rustflags.push("-C");
rustflags.push("split-debuginfo=packed");
}
Ok(())
}
fn create_build_command(
context: &BuildContext,
cargo_rustc: cargo_options::Rustc,
target_triple: &str,
) -> Result<Command> {
let target = &context.project.target;
let mut build_command = if target.is_msvc() && target.cross_compiling() {
#[cfg(feature = "xwin")]
{
let native_compile = target.host_triple().contains("windows-msvc")
&& cc::Build::new()
.opt_level(0)
.host(target.host_triple())
.target(target_triple)
.cargo_metadata(false)
.cargo_warnings(false)
.cargo_output(false)
.try_get_compiler()
.is_ok();
let force_xwin = env::var("MATURIN_USE_XWIN").ok().as_deref() == Some("1");
if !native_compile || force_xwin {
println!("🛠️ Using xwin for cross-compiling to {target_triple}");
let xwin_options = {
use clap::Parser;
cargo_xwin::XWinOptions::parse_from(Vec::<&str>::new())
};
let mut build = cargo_xwin::Rustc::from(cargo_rustc);
build.target = vec![target_triple.to_string()];
build.xwin = xwin_options;
build.build_command()?
} else {
let mut cargo_rustc = cargo_rustc;
if target.user_specified {
cargo_rustc.target = vec![target_triple.to_string()];
}
cargo_rustc.command()
}
}
#[cfg(not(feature = "xwin"))]
{
let mut cargo_rustc = cargo_rustc;
if target.user_specified {
cargo_rustc.target = vec![target_triple.to_string()];
}
cargo_rustc.command()
}
} else {
#[cfg(feature = "zig")]
{
let mut build = cargo_zigbuild::Rustc::from(cargo_rustc);
if !context.python.zig {
build.disable_zig_linker = true;
if target.user_specified {
build.target = vec![target_triple.to_string()];
}
} else {
println!("🛠️ Using zig for cross-compiling to {target_triple}");
build.enable_zig_ar = true;
let zig_triple = if target.is_linux() && !target.is_musl_libc() {
match context
.python
.platform_tag
.iter()
.find(|tag| tag.is_manylinux())
{
Some(PlatformTag::Manylinux { major, minor }) => {
format!("{target_triple}.{major}.{minor}")
}
_ => target_triple.to_string(),
}
} else {
target_triple.to_string()
};
build.target = vec![zig_triple];
}
build.build_command()?
}
#[cfg(not(feature = "zig"))]
{
let mut cargo_rustc = cargo_rustc;
if target.user_specified {
cargo_rustc.target = vec![target_triple.to_string()];
}
cargo_rustc.command()
}
};
#[cfg(feature = "zig")]
if context.python.zig {
if let Ok((zig_cmd, zig_args)) = cargo_zigbuild::Zig::find_zig() {
if zig_args.is_empty() {
build_command.env("ZIG_COMMAND", zig_cmd);
} else {
build_command.env(
"ZIG_COMMAND",
format!("{} {}", zig_cmd.display(), zig_args.join(" ")),
);
};
}
}
build_command
.stdout(Stdio::piped())
.stderr(Stdio::inherit());
Ok(build_command)
}
fn configure_pyo3_env(
build_command: &mut Command,
context: &BuildContext,
python_interpreter: Option<&PythonInterpreter>,
bridge_model: &BridgeModel,
target: &Target,
target_triple: &str,
) -> Result<()> {
if bridge_model.is_abi3() {
let is_pypy_or_graalpy = python_interpreter
.map(|p| p.interpreter_kind.is_pypy() || p.interpreter_kind.is_graalpy())
.unwrap_or(false);
if !is_pypy_or_graalpy && !target.is_windows() {
let pyo3_ver = pyo3_version(&context.project.cargo_metadata)
.context("Failed to get pyo3 version from cargo metadata")?;
if pyo3_ver < PYO3_ABI3_NO_PYTHON_VERSION {
build_command.env("PYO3_NO_PYTHON", "1");
}
}
}
if bridge_model.is_pyo3() && !bridge_model.is_bin() {
build_command.env("PYO3_BUILD_EXTENSION_MODULE", "1");
}
if let Some(interpreter) = python_interpreter {
if interpreter.runnable {
if bridge_model.is_pyo3() {
debug!(
"Setting PYO3_PYTHON to {}",
interpreter.executable.display()
);
build_command
.env("PYO3_PYTHON", &interpreter.executable)
.env(
"PYO3_ENVIRONMENT_SIGNATURE",
interpreter.environment_signature(),
);
}
build_command.env("PYTHON_SYS_EXECUTABLE", &interpreter.executable);
} else if bridge_model.is_pyo3() && env::var_os("PYO3_CONFIG_FILE").is_none() {
let pyo3_config = interpreter.pyo3_config_file();
let maturin_target_dir = ensure_target_maturin_dir(&context.project.target_dir);
let config_file = maturin_target_dir.join(format!(
"pyo3-config-{}-{}.{}.txt",
target_triple, interpreter.major, interpreter.minor
));
let existing_pyo3_config = fs::read_to_string(&config_file).unwrap_or_default();
if pyo3_config != existing_pyo3_config {
fs::write(&config_file, pyo3_config).with_context(|| {
format!(
"Failed to create pyo3 config file at '{}'",
config_file.display()
)
})?;
}
let abs_config_file = config_file.normalize()?.into_path_buf();
build_command.env("PYO3_CONFIG_FILE", abs_config_file);
}
}
if !context.project.editable
&& target.is_macos()
&& env::var_os("MACOSX_DEPLOYMENT_TARGET").is_none()
{
let target_config = context
.project
.pyproject_toml
.as_ref()
.and_then(|x| x.target_config(target_triple));
let deployment_target = if let Some(deployment_target) = target_config
.as_ref()
.and_then(|config| config.macos_deployment_target.as_ref())
{
eprintln!(
"💻 Using `MACOSX_DEPLOYMENT_TARGET={deployment_target}` for {target_triple} by configuration"
);
deployment_target.clone()
} else {
let (major, minor) = rustc_macosx_target_version(target_triple);
eprintln!(
"💻 Using `MACOSX_DEPLOYMENT_TARGET={major}.{minor}` for {target_triple} by default"
);
format!("{major}.{minor}")
};
build_command.env("MACOSX_DEPLOYMENT_TARGET", deployment_target);
}
Ok(())
}
fn compile_target(
context: &BuildContext,
mut build_command: Command,
) -> Result<(HashMap<CrateType, BuildArtifact>, HashMap<String, PathBuf>)> {
debug!("Running {:?}", build_command);
let using_cross = build_command
.get_program()
.to_string_lossy()
.starts_with("cross");
let mut cargo_build = build_command
.spawn()
.context("Failed to run `cargo rustc`")?;
let mut artifacts = HashMap::new();
let mut linked_paths = Vec::new();
let mut out_dirs: HashMap<String, PathBuf> = HashMap::new();
let stream = cargo_build
.stdout
.take()
.expect("Cargo build should have a stdout");
for message in cargo_metadata::Message::parse_stream(BufReader::new(stream)) {
let message = message.context("Failed to parse cargo metadata message")?;
trace!("cargo message: {:?}", message);
match message {
cargo_metadata::Message::CompilerArtifact(artifact) => {
let package_in_metadata = context
.project
.cargo_metadata
.packages
.iter()
.find(|package| package.id == artifact.package_id);
let crate_name = match package_in_metadata {
Some(package) => &package.name,
None => {
let package_id = &artifact.package_id;
let should_warn = !package_id.repr.contains("rustup")
&& !package_id.repr.contains("rustlib")
&& !artifact.features.contains(&"rustc-dep-of-std".to_string());
if should_warn {
eprintln!(
"⚠️ Warning: The package {package_id} wasn't listed in `cargo metadata`"
);
}
continue;
}
};
if crate_name.as_ref() == context.project.crate_name {
let num_crate_types = artifact.target.crate_types.len();
let mut filenames_iter = artifact.filenames.into_iter();
for crate_type in artifact.target.crate_types {
if let Some(filename) = filenames_iter.next() {
let path = if using_cross && filename.starts_with("/target") {
context
.project
.cargo_metadata
.target_directory
.join(filename.strip_prefix("/target").unwrap())
.into_std_path_buf()
} else {
filename.into()
};
let artifact = BuildArtifact {
path,
import_lib_path: None,
debuginfo_path: None,
linked_paths: Vec::new(),
thin_artifacts: Vec::new(),
};
artifacts.insert(crate_type, artifact);
}
}
if num_crate_types == 1
&& let Some((_, artifact)) = artifacts.iter_mut().next()
{
for extra in filenames_iter {
let extra_path: PathBuf = extra.into();
if let Some(ext) = extra_path.extension() {
if (ext == "lib" || ext == "a")
&& artifact.import_lib_path.is_none()
{
artifact.import_lib_path = Some(extra_path);
} else if (ext == "pdb" || ext == "dSYM" || ext == "dwp")
&& artifact.debuginfo_path.is_none()
{
artifact.debuginfo_path = Some(extra_path);
}
}
}
}
}
}
cargo_metadata::Message::BuildScriptExecuted(msg) => {
let has_dylib = msg.linked_libs.iter().map(|l| l.as_str()).any(|lib| {
if let Some(index) = lib.find('=') {
let kind = &lib[..index];
kind.starts_with("dylib")
} else {
true
}
});
if has_dylib {
for path in msg.linked_paths.iter().map(|p| p.as_str()) {
if let Some(index) = path.find('=') {
let kind = &path[..index];
if kind == "native" || kind == "all" {
linked_paths.push(path[index + 1..].to_string());
}
} else {
linked_paths.push(path.to_string());
}
}
}
if !msg.out_dir.as_os_str().is_empty() {
let pkg_name = context
.project
.cargo_metadata
.packages
.iter()
.find(|p| p.id == msg.package_id)
.map(|p| p.name.clone());
if let Some(name) = pkg_name {
out_dirs.insert(name.to_string(), msg.out_dir.into_std_path_buf());
}
}
}
cargo_metadata::Message::CompilerMessage(msg) => {
println!("{}", msg.message);
}
_ => (),
}
}
for artifact in artifacts.values_mut() {
artifact.linked_paths.clone_from(&linked_paths);
}
let status = cargo_build
.wait()
.expect("Failed to wait on cargo child process");
if !status.success() {
bail!(
r#"Cargo build finished with "{}": `{:?}`"#,
status,
build_command,
)
}
Ok((artifacts, out_dirs))
}
#[instrument(skip_all)]
pub fn warn_missing_py_init(artifact: &Path, module_name: &str) -> Result<()> {
let py_init = format!("PyInit_{module_name}");
let fd = File::open(artifact)?;
let mmap = unsafe { memmap2::Mmap::map(&fd).context("mmap failed")? };
let mut found = false;
match goblin::Object::parse(&mmap)? {
goblin::Object::Elf(elf) => {
for dyn_sym in elf.dynsyms.iter() {
if py_init == elf.dynstrtab[dyn_sym.st_name] {
found = true;
break;
}
}
}
goblin::Object::Mach(mach) => {
match mach {
goblin::mach::Mach::Binary(macho) => {
for sym in macho.exports()? {
let sym_name = sym.name;
if py_init == sym_name.strip_prefix('_').unwrap_or(&sym_name) {
found = true;
break;
}
}
if !found {
for sym in macho.symbols() {
let (sym_name, _) = sym?;
if py_init == sym_name.strip_prefix('_').unwrap_or(sym_name) {
found = true;
break;
}
}
}
}
goblin::mach::Mach::Fat(_) => {
found = true
}
}
}
goblin::Object::PE(pe) => {
for sym in &pe.exports {
if let Some(sym_name) = sym.name
&& py_init == sym_name
{
found = true;
break;
}
}
}
_ => {
found = true
}
}
if !found {
eprintln!(
"⚠️ Warning: Couldn't find the symbol `{py_init}` in the native library. \
Python will fail to import this module. \
If you're using pyo3, check that `#[pymodule]` uses `{module_name}` as module name"
)
}
Ok(())
}
pub(crate) fn ensure_target_maturin_dir(target_dir: &Path) -> PathBuf {
let dir = target_dir.join(env!("CARGO_PKG_NAME"));
let _ = fs::create_dir_all(&dir);
dir
}
fn pyo3_version(cargo_metadata: &cargo_metadata::Metadata) -> Option<(u64, u64, u64)> {
let packages: HashMap<&str, &cargo_metadata::Package> = cargo_metadata
.packages
.iter()
.filter_map(|pkg| {
let name = pkg.name.as_ref();
if name == "pyo3" || name == "pyo3-ffi" {
Some((name, pkg))
} else {
None
}
})
.collect();
packages
.get("pyo3")
.or_else(|| packages.get("pyo3-ffi"))
.map(|pkg| (pkg.version.major, pkg.version.minor, pkg.version.patch))
}