diff --git a/guide/src/config.md b/guide/src/config.md index 89e6daa58..4b5d29384 100644 --- a/guide/src/config.md +++ b/guide/src/config.md @@ -63,6 +63,10 @@ strip = true # Source distribution generator, # supports cargo (default) and git. sdist-generator = "cargo" +# Include the Windows import library (.dll.lib or .dll.a) in the wheel. +# This is useful when distributing shared libraries that other programs +# need to link against at compile time. +include-import-lib = false # Use base Python executable instead of venv Python executable in PEP 517 build. # # This can help avoid unnecessary rebuilds, as the Python executable does not change diff --git a/maturin.schema.json b/maturin.schema.json index 278b5dd3c..d29123368 100644 --- a/maturin.schema.json +++ b/maturin.schema.json @@ -101,6 +101,11 @@ "$ref": "#/$defs/GlobPattern" } }, + "include-import-lib": { + "description": "Include the import library (.dll.lib) in the wheel on Windows", + "type": "boolean", + "default": false + }, "locked": { "description": "Require Cargo.lock is up to date", "type": [ diff --git a/src/binding_generator/mod.rs b/src/binding_generator/mod.rs index b57601bfe..8f16f5fe3 100644 --- a/src/binding_generator/mod.rs +++ b/src/binding_generator/mod.rs @@ -193,6 +193,16 @@ where } } } + + // 4a. Install import library on Windows + if let Some(import_lib) = &artifact.import_lib_path + && context.include_import_lib + { + let target = base_path.join(import_lib.file_name().unwrap()); + fs::create_dir_all(target.parent().unwrap())?; + debug!("Installing import library {}", target.display()); + fs::copy(import_lib, &target)?; + } } _ => { // 2b. Install the artifact @@ -213,6 +223,15 @@ where writer.add_entry_force(target, source)?; } } + + // 4b. Install import library on Windows + if let Some(import_lib) = &artifact.import_lib_path + && context.include_import_lib + { + let dest = module.join(import_lib.file_name().unwrap()); + debug!("Adding import library to archive {}", dest.display()); + writer.add_file_force(dest, import_lib, false)?; + } } } } diff --git a/src/build_context.rs b/src/build_context.rs index bd9836c7b..5bc1253a6 100644 --- a/src/build_context.rs +++ b/src/build_context.rs @@ -147,6 +147,8 @@ pub struct BuildContext { pub pypi_validation: bool, /// SBOM configuration pub sbom: Option, + /// Include the import library (.dll.lib) in the wheel on Windows + pub include_import_lib: bool, } /// The wheel file location and its Python version tag (e.g. `py3`). diff --git a/src/build_options.rs b/src/build_options.rs index c5477faba..0a15ce850 100644 --- a/src/build_options.rs +++ b/src/build_options.rs @@ -887,6 +887,9 @@ impl BuildContextBuilder { } let crate_name = cargo_toml.package.name; + let include_import_lib = pyproject + .map(|p| p.include_import_lib()) + .unwrap_or_default(); Ok(BuildContext { target, compile_targets, @@ -912,6 +915,7 @@ impl BuildContextBuilder { compression: build_options.compression, pypi_validation, sbom, + include_import_lib, }) } } diff --git a/src/compile.rs b/src/compile.rs index 5cfac91ca..6d4424c97 100644 --- a/src/compile.rs +++ b/src/compile.rs @@ -41,6 +41,8 @@ pub struct CompileTarget { pub struct BuildArtifact { /// Path to the build artifact pub path: PathBuf, + /// Path to the Windows import library (.dll.lib or .dll.a), if any + pub import_lib_path: Option, /// Array of paths to include in the library search path, as indicated by /// the `cargo:rustc-link-search` instruction. pub linked_paths: Vec, @@ -143,6 +145,7 @@ fn compile_universal2( let mut result = HashMap::new(); let universal_artifact = BuildArtifact { path: PathBuf::from(output_path), + import_lib_path: None, linked_paths: x86_64_artifact.linked_paths.clone(), }; result.insert(build_type, universal_artifact); @@ -564,27 +567,42 @@ fn compile_target( // Extract the location of the .so/.dll/etc. from cargo's json output if crate_name.as_ref() == context.crate_name { - let tuples = artifact - .target - .crate_types - .into_iter() - .zip(artifact.filenames); - for (crate_type, filename) in tuples { - let path = if using_cross && filename.starts_with("/target") { - // Convert cross target path in docker back to path on host - context - .cargo_metadata - .target_directory - .join(filename.strip_prefix("/target").unwrap()) - .into_std_path_buf() - } else { - filename.into() - }; - let artifact = BuildArtifact { - path, - linked_paths: Vec::new(), - }; - artifacts.insert(crate_type, artifact); + 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") { + // Convert cross target path in docker back to path on host + context + .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, + linked_paths: Vec::new(), + }; + artifacts.insert(crate_type, artifact); + } + } + // Remaining filenames may include import libraries (.dll.lib, .dll.a) + if num_crate_types == 1 + && let Some((_, artifact)) = artifacts.iter_mut().next() + { + for extra in filenames_iter { + let extra_path: PathBuf = extra.into(); + if extra_path + .extension() + .is_some_and(|ext| ext == "lib" || ext == "a") + { + artifact.import_lib_path = Some(extra_path); + break; + } + } } } } diff --git a/src/pyproject_toml.rs b/src/pyproject_toml.rs index 4d3ec9ee7..9723e77bb 100644 --- a/src/pyproject_toml.rs +++ b/src/pyproject_toml.rs @@ -306,6 +306,9 @@ pub struct ToolMaturin { pub use_base_python: bool, /// SBOM configuration pub sbom: Option, + /// Include the import library (.dll.lib) in the wheel on Windows + #[serde(default)] + pub include_import_lib: bool, } /// A pyproject.toml as specified in PEP 517 @@ -440,6 +443,13 @@ impl PyProjectToml { self.maturin()?.manifest_path.as_deref() } + /// Returns the value of `[tool.maturin.include-import-lib]` in pyproject.toml + pub fn include_import_lib(&self) -> bool { + self.maturin() + .map(|maturin| maturin.include_import_lib) + .unwrap_or_default() + } + /// Warn about `build-system.requires` mismatching expectations. /// /// Having a pyproject.toml without a version constraint is a bad idea diff --git a/test-crates/cffi-pure/pyproject.toml b/test-crates/cffi-pure/pyproject.toml index 01123a478..12dc58570 100644 --- a/test-crates/cffi-pure/pyproject.toml +++ b/test-crates/cffi-pure/pyproject.toml @@ -9,3 +9,4 @@ dynamic = ["version"] [tool.maturin] bindings = "cffi" +include-import-lib = true