diff --git a/Changelog.md b/Changelog.md index 7a2fd09b7..8c00f2631 100644 --- a/Changelog.md +++ b/Changelog.md @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Fixes the compatibility tags for Pyston. * Set default macOS deployment target version if `MACOSX_DEPLOYMENT_TARGET` isn't specified in [#1251](https://github.com/PyO3/maturin/pull/1251) * Add support for 32-bit x86 FreeBSD target in [#1254](https://github.com/PyO3/maturin/pull/1254) +* Add `[tool.maturin.include]` and `[tool.maturin.exclude]` and deprecate `[tool.maturin.sdist-include]` [#1255](https://github.com/PyO3/maturin/pull/1255) ## [0.13.7] - 2022-10-29 diff --git a/README.md b/README.md index be600e370..83c20ff4d 100644 --- a/README.md +++ b/README.md @@ -223,11 +223,11 @@ compatibility = "linux" `manylinux` option is also accepted as an alias of `compatibility` for backward compatibility with old version of maturin. -To include arbitrary files in the sdist for use during compilation specify `sdist-include` as an array of globs: +To include arbitrary files in the sdist for use during compilation specify `include` as an array of `path` globs with `format` set to `sdist`: ```toml [tool.maturin] -sdist-include = ["path/**/*"] +include = [{ path = "path/**/*", format = "sdist" }] ``` There's a `maturin sdist` command for only building a source distribution as workaround for [pypa/pip#6041](https://github.com/pypa/pip/issues/6041). diff --git a/guide/src/distribution.md b/guide/src/distribution.md index 22ad2304f..421bc9521 100644 --- a/guide/src/distribution.md +++ b/guide/src/distribution.md @@ -32,11 +32,11 @@ compatibility = "linux" `manylinux` option is also accepted as an alias of `compatibility` for backward compatibility with old version of maturin. -To include arbitrary files in the sdist for use during compilation specify `sdist-include` as an array of globs: +To include arbitrary files in the sdist for use during compilation specify `include` as an array of `path` globs with `format` set to `sdist`: ```toml [tool.maturin] -sdist-include = ["path/**/*"] +include = [{ path = "path/**/*", format = "sdist" }] ``` ## Build Wheels diff --git a/guide/src/metadata.md b/guide/src/metadata.md index 0b10ac556..f0ed3ff24 100644 --- a/guide/src/metadata.md +++ b/guide/src/metadata.md @@ -108,7 +108,12 @@ in the `tool.maturin` section of `pyproject.toml`. ```toml [tool.maturin] # Include arbitrary files in the sdist +# NOTE: deprecated, please use `include` with `format="sdist"` sdist-include = [] +# Include additional files +include = [] +# Exclude files +exclude = [] # Bindings type bindings = "pyo3" # Control the platform tag on linux @@ -138,3 +143,26 @@ unstable-flags = [] # Extra arguments that will be passed to rustc as `cargo rustc [...] -- [...] [arg1] [arg2]` rustc-args = [] ``` + +The `[tool.maturin.include]` and `[tool.maturin.exclude]` configuration are +inspired by +[Poetry](https://python-poetry.org/docs/pyproject/#include-and-exclude). + +To specify files or globs directly: + +```toml +include = ["path/**/*", "some/other/file"] +``` + +To specify a specific target format (`sdist` or `wheel`): + +```toml +include = [ + { path = "path/**/*", format = "sdist" }, + { path = "all", format = ["sdist", "wheel"] }, + { path = "for/wheel/**/*", format = "wheel" } +] +``` + +The default behavior is apply these configurations to both `sdist` and `wheel` +targets. diff --git a/src/build_context.rs b/src/build_context.rs index 37ed2369b..c50124073 100644 --- a/src/build_context.rs +++ b/src/build_context.rs @@ -10,12 +10,15 @@ use crate::project_layout::ProjectLayout; use crate::python_interpreter::InterpreterKind; use crate::source_distribution::source_distribution; use crate::{ - compile, BuildArtifact, Metadata21, ModuleWriter, PyProjectToml, PythonInterpreter, Target, + compile, pyproject_toml::Format, BuildArtifact, Metadata21, ModuleWriter, PyProjectToml, + PythonInterpreter, Target, }; use anyhow::{anyhow, bail, Context, Result}; use cargo_metadata::Metadata; use fs_err as fs; +use ignore::overrides::{Override, OverrideBuilder}; use lddtree::Library; +use normpath::PathExt; use regex::Regex; use sha2::{Digest, Sha256}; use std::collections::{HashMap, HashSet}; @@ -249,8 +252,9 @@ impl BuildContext { match self.pyproject_toml.as_ref() { Some(pyproject) => { - let sdist_path = source_distribution(self, pyproject) - .context("Failed to build source distribution")?; + let sdist_path = + source_distribution(self, pyproject, self.excludes(Format::Sdist)?) + .context("Failed to build source distribution")?; Ok(Some((sdist_path, "source".to_string()))) } None => Ok(None), @@ -450,6 +454,23 @@ impl BuildContext { Ok(()) } + fn excludes(&self, format: Format) -> Result> { + if let Some(pyproject) = self.pyproject_toml.as_ref() { + let pyproject_dir = self.pyproject_toml_path.normalize()?.into_path_buf(); + if let Some(glob_patterns) = &pyproject.exclude() { + let mut excludes = OverrideBuilder::new(pyproject_dir.parent().unwrap()); + for glob in glob_patterns + .iter() + .filter_map(|glob_pattern| glob_pattern.targets(format)) + { + excludes.add(glob)?; + } + return Ok(Some(excludes.build()?)); + } + } + Ok(None) + } + fn write_binding_wheel_abi3( &self, artifact: BuildArtifact, @@ -463,7 +484,13 @@ impl BuildContext { .get_platform_tag(platform_tags, self.universal2)?; let tag = format!("cp{}{}-abi3-{}", major, min_minor, platform); - let mut writer = WheelWriter::new(&tag, &self.out, &self.metadata21, &[tag.clone()])?; + let mut writer = WheelWriter::new( + &tag, + &self.out, + &self.metadata21, + &[tag.clone()], + self.excludes(Format::Wheel)?, + )?; self.add_external_libs(&mut writer, &[&artifact], &[ext_libs])?; write_bindings_module( @@ -474,6 +501,7 @@ impl BuildContext { None, &self.target, self.editable, + self.pyproject_toml.as_ref(), ) .context("Failed to add the files to the wheel")?; @@ -534,7 +562,13 @@ impl BuildContext { ) -> Result { let tag = python_interpreter.get_tag(&self.target, platform_tags, self.universal2)?; - let mut writer = WheelWriter::new(&tag, &self.out, &self.metadata21, &[tag.clone()])?; + let mut writer = WheelWriter::new( + &tag, + &self.out, + &self.metadata21, + &[tag.clone()], + self.excludes(Format::Wheel)?, + )?; self.add_external_libs(&mut writer, &[&artifact], &[ext_libs])?; write_bindings_module( @@ -545,6 +579,7 @@ impl BuildContext { Some(python_interpreter), &self.target, self.editable, + self.pyproject_toml.as_ref(), ) .context("Failed to add the files to the wheel")?; @@ -651,7 +686,13 @@ impl BuildContext { .target .get_universal_tags(platform_tags, self.universal2)?; - let mut writer = WheelWriter::new(&tag, &self.out, &self.metadata21, &tags)?; + let mut writer = WheelWriter::new( + &tag, + &self.out, + &self.metadata21, + &tags, + self.excludes(Format::Wheel)?, + )?; self.add_external_libs(&mut writer, &[&artifact], &[ext_libs])?; write_cffi_module( @@ -663,6 +704,7 @@ impl BuildContext { &artifact.path, &self.interpreter[0].executable, self.editable, + self.pyproject_toml.as_ref(), )?; self.add_pth(&mut writer)?; @@ -754,7 +796,13 @@ impl BuildContext { self.metadata21.clone() }; - let mut writer = WheelWriter::new(&tag, &self.out, &metadata21, &tags)?; + let mut writer = WheelWriter::new( + &tag, + &self.out, + &metadata21, + &tags, + self.excludes(Format::Wheel)?, + )?; if let Some(python_module) = &self.project_layout.python_module { if self.target.is_wasi() { @@ -763,7 +811,7 @@ impl BuildContext { bail!("Sorry, adding python code to a wasm binary is currently not supported") } if !self.editable { - write_python_part(&mut writer, python_module) + write_python_part(&mut writer, python_module, self.pyproject_toml.as_ref()) .context("Failed to add the python module to the package")?; } } diff --git a/src/lib.rs b/src/lib.rs index 89fb65583..91a37c97a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -54,7 +54,7 @@ mod module_writer; mod new_project; mod polyfill; mod project_layout; -mod pyproject_toml; +pub mod pyproject_toml; mod python_interpreter; mod source_distribution; mod target; diff --git a/src/module_writer.rs b/src/module_writer.rs index 00cf053ed..e65ce757b 100644 --- a/src/module_writer.rs +++ b/src/module_writer.rs @@ -1,11 +1,14 @@ //! The wheel format is (mostly) specified in PEP 427 use crate::project_layout::ProjectLayout; -use crate::{BridgeModel, Metadata21, PythonInterpreter, Target}; +use crate::{ + pyproject_toml::Format, BridgeModel, Metadata21, PyProjectToml, PythonInterpreter, Target, +}; use anyhow::{anyhow, bail, Context, Result}; use flate2::write::GzEncoder; use flate2::Compression; use fs_err as fs; use fs_err::File; +use ignore::overrides::Override; use ignore::WalkBuilder; use normpath::PathExt as _; use sha2::{Digest, Sha256}; @@ -202,6 +205,7 @@ pub struct WheelWriter { record: Vec<(String, String, usize)>, record_file: PathBuf, wheel_path: PathBuf, + excludes: Option, } impl ModuleWriter for WheelWriter { @@ -215,8 +219,12 @@ impl ModuleWriter for WheelWriter { bytes: &[u8], permissions: u32, ) -> Result<()> { + let target = target.as_ref(); + if self.exclude(target) { + return Ok(()); + } // The zip standard mandates using unix style paths - let target = target.as_ref().to_str().unwrap().replace('\\', "/"); + let target = target.to_str().unwrap().replace('\\', "/"); // Unlike users which can use the develop subcommand, the tests have to go through // packing a zip which pip than has to unpack. This makes this 2-3 times faster @@ -247,6 +255,7 @@ impl WheelWriter { wheel_dir: &Path, metadata21: &Metadata21, tags: &[String], + excludes: Option, ) -> Result { let wheel_path = wheel_dir.join(format!( "{}-{}-{}.whl", @@ -262,6 +271,7 @@ impl WheelWriter { record: Vec::new(), record_file: metadata21.get_dist_info_dir().join("RECORD"), wheel_path, + excludes, }; write_dist_info(&mut builder, metadata21, tags)?; @@ -289,6 +299,15 @@ impl WheelWriter { Ok(()) } + /// Returns `true` if the given path should be excluded + fn exclude(&self, path: impl AsRef) -> bool { + if let Some(excludes) = &self.excludes { + excludes.matched(path.as_ref(), false).is_whitelist() + } else { + false + } + } + /// Creates the record file and finishes the zip pub fn finish(mut self) -> Result { let compression_method = if cfg!(feature = "faster-tests") { @@ -318,6 +337,7 @@ pub struct SDistWriter { tar: tar::Builder>, path: PathBuf, files: HashSet, + excludes: Option, } impl ModuleWriter for SDistWriter { @@ -332,6 +352,10 @@ impl ModuleWriter for SDistWriter { permissions: u32, ) -> Result<()> { let target = target.as_ref(); + if self.exclude(target) { + return Ok(()); + } + if self.files.contains(target) { // Ignore duplicate files return Ok(()); @@ -354,6 +378,9 @@ impl ModuleWriter for SDistWriter { fn add_file(&mut self, target: impl AsRef, source: impl AsRef) -> Result<()> { let source = source.as_ref(); + if self.exclude(source) { + return Ok(()); + } let target = target.as_ref(); if source == self.path { bail!( @@ -381,7 +408,11 @@ impl ModuleWriter for SDistWriter { impl SDistWriter { /// Create a source distribution .tar.gz which can be subsequently expanded - pub fn new(wheel_dir: impl AsRef, metadata21: &Metadata21) -> Result { + pub fn new( + wheel_dir: impl AsRef, + metadata21: &Metadata21, + excludes: Option, + ) -> Result { let path = wheel_dir.as_ref().join(format!( "{}-{}.tar.gz", &metadata21.get_distribution_escaped(), @@ -396,9 +427,19 @@ impl SDistWriter { tar, path, files: HashSet::new(), + excludes, }) } + /// Returns `true` if the given path should be excluded + fn exclude(&self, path: impl AsRef) -> bool { + if let Some(excludes) = &self.excludes { + excludes.matched(path.as_ref(), false).is_whitelist() + } else { + false + } + } + /// Finished the .tar.gz archive pub fn finish(mut self) -> Result { self.tar.finish()?; @@ -634,6 +675,7 @@ pub fn write_bindings_module( python_interpreter: Option<&PythonInterpreter>, target: &Target, editable: bool, + pyproject_toml: Option<&PyProjectToml>, ) -> Result<()> { let ext_name = &project_layout.extension_name; let so_filename = match python_interpreter { @@ -664,7 +706,7 @@ pub fn write_bindings_module( target.display() ))?; } else { - write_python_part(writer, python_module) + write_python_part(writer, python_module, pyproject_toml) .context("Failed to add the python module to the package")?; let relative = project_layout @@ -714,6 +756,7 @@ pub fn write_cffi_module( artifact: &Path, python: &Path, editable: bool, + pyproject_toml: Option<&PyProjectToml>, ) -> Result<()> { let cffi_declarations = generate_cffi_declarations(crate_dir, target_dir, python)?; @@ -721,7 +764,7 @@ pub fn write_cffi_module( if let Some(python_module) = &project_layout.python_module { if !editable { - write_python_part(writer, python_module) + write_python_part(writer, python_module, pyproject_toml) .context("Failed to add the python module to the package")?; } @@ -848,11 +891,13 @@ if __name__ == '__main__': pub fn write_python_part( writer: &mut impl ModuleWriter, python_module: impl AsRef, + pyproject_toml: Option<&PyProjectToml>, ) -> Result<()> { - for absolute in WalkBuilder::new(&python_module).hidden(false).build() { + let python_module = python_module.as_ref(); + for absolute in WalkBuilder::new(python_module).hidden(false).build() { let absolute = absolute?.into_path(); let relative = absolute - .strip_prefix(python_module.as_ref().parent().unwrap()) + .strip_prefix(python_module.parent().unwrap()) .unwrap(); if absolute.is_dir() { writer.add_directory(relative)?; @@ -870,6 +915,30 @@ pub fn write_python_part( } } + // Include additional files + if let Some(pyproject) = pyproject_toml { + let pyproject_dir = python_module.parent().unwrap(); + if let Some(glob_patterns) = pyproject.include() { + for pattern in glob_patterns + .iter() + .filter_map(|glob_pattern| glob_pattern.targets(Format::Sdist)) + { + println!("📦 Including files matching \"{}\"", pattern); + for source in glob::glob(&pyproject_dir.join(pattern).to_string_lossy()) + .expect("No files found for pattern") + .filter_map(Result::ok) + { + let target = source.strip_prefix(pyproject_dir)?.to_path_buf(); + if source.is_dir() { + writer.add_directory(target)?; + } else { + writer.add_file(target, source)?; + } + } + } + } + } + Ok(()) } @@ -972,3 +1041,44 @@ pub fn add_data(writer: &mut impl ModuleWriter, data: Option<&Path>) -> Result<( } Ok(()) } + +#[cfg(test)] +mod tests { + use ignore::overrides::OverrideBuilder; + + use super::*; + + #[test] + // The mechanism is the same for wheel_writer + fn sdist_writer_excludes() -> Result<(), Box> { + let metadata = Metadata21::default(); + let perm = 0o777; + + // No excludes + let tmp_dir = TempDir::new()?; + let mut writer = SDistWriter::new(&tmp_dir, &metadata, None)?; + assert!(writer.files.is_empty()); + writer.add_bytes_with_permissions("test", &[], perm)?; + assert_eq!(writer.files.len(), 1); + writer.finish()?; + tmp_dir.close()?; + + // A test filter + let tmp_dir = TempDir::new()?; + let mut excludes = OverrideBuilder::new(&tmp_dir); + excludes.add("test*")?; + excludes.add("!test2")?; + let mut writer = SDistWriter::new(&tmp_dir, &metadata, Some(excludes.build()?))?; + writer.add_bytes_with_permissions("test1", &[], perm)?; + writer.add_bytes_with_permissions("test3", &[], perm)?; + assert!(writer.files.is_empty()); + writer.add_bytes_with_permissions("test2", &[], perm)?; + assert!(!writer.files.is_empty()); + writer.add_bytes_with_permissions("yes", &[], perm)?; + assert_eq!(writer.files.len(), 2); + writer.finish()?; + tmp_dir.close()?; + + Ok(()) + } +} diff --git a/src/pyproject_toml.rs b/src/pyproject_toml.rs index cd0629ccb..60cd5f3b5 100644 --- a/src/pyproject_toml.rs +++ b/src/pyproject_toml.rs @@ -1,3 +1,5 @@ +//! A pyproject.toml as specified in PEP 517 + use crate::PlatformTag; use anyhow::{format_err, Result}; use fs_err as fs; @@ -12,12 +14,82 @@ pub struct Tool { maturin: Option, } +#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +/// The target format for the include or exclude [GlobPattern]. +/// +/// See [Formats]. +pub enum Format { + /// Source distribution + Sdist, + /// Wheel + Wheel, +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[serde(untagged)] +/// A single [Format] or multiple [Format] values for a [GlobPattern]. +pub enum Formats { + /// A single [Format] value + Single(Format), + /// Multiple [Format] values + Multiple(Vec), +} + +impl Formats { + /// Returns `true` if the inner [Format] value(s) target the given [Format] + pub fn targets(&self, format: Format) -> bool { + match self { + Self::Single(val) if val == &format => true, + Self::Multiple(formats) if formats.contains(&format) => true, + _ => false, + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[serde(untagged)] +/// A glob pattern for the include and exclude configuration. +/// +/// See [PyProjectToml::include] and [PyProject::exclude]. +/// +/// Based on . +pub enum GlobPattern { + /// A glob + Path(String), + /// A glob `path` with a `format` key to specify one or more [Format] values + WithFormat { + /// A glob + path: String, + /// One or more [Format] values + format: Formats, + }, +} + +impl GlobPattern { + /// Returns the glob pattern for this pattern if it targets the given [Format], else this returns `None`. + pub fn targets(&self, format: Format) -> Option<&str> { + match self { + // Not specified defaults to both + Self::Path(ref glob) => Some(glob), + Self::WithFormat { + path, + format: formats, + } if formats.targets(format) => Some(path), + _ => None, + } + } +} + /// The `[tool.maturin]` section of a pyproject.toml #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "kebab-case")] pub struct ToolMaturin { // maturin specific options + // TODO(0.15.0): remove deprecated sdist_include: Option>, + include: Option>, + exclude: Option>, bindings: Option, #[serde(alias = "manylinux")] compatibility: Option, @@ -48,6 +120,7 @@ pub struct ToolMaturin { pub config: Option>, /// Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details pub unstable_flags: Option>, + /// Additional rustc arguments pub rustc_args: Option>, } @@ -98,10 +171,24 @@ impl PyProjectToml { } /// Returns the value of `[tool.maturin.sdist-include]` in pyproject.toml + #[deprecated( + since = "0.14.0", + note = "please use `PyProjectToml::include` ()" + )] pub fn sdist_include(&self) -> Option<&Vec> { self.maturin()?.sdist_include.as_ref() } + /// Returns the value of `[tool.maturin.include]` in pyproject.toml + pub fn include(&self) -> Option<&[GlobPattern]> { + self.maturin()?.include.as_ref().map(AsRef::as_ref) + } + + /// Returns the value of `[tool.maturin.exclude]` in pyproject.toml + pub fn exclude(&self) -> Option<&[GlobPattern]> { + self.maturin()?.exclude.as_ref().map(AsRef::as_ref) + } + /// Returns the value of `[tool.maturin.bindings]` in pyproject.toml pub fn bindings(&self) -> Option<&str> { self.maturin()?.bindings.as_deref() @@ -193,7 +280,10 @@ impl PyProjectToml { #[cfg(test)] mod tests { - use crate::PyProjectToml; + use crate::{ + pyproject_toml::{Format, Formats, GlobPattern, ToolMaturin}, + PyProjectToml, + }; use fs_err as fs; use pretty_assertions::assert_eq; use std::path::Path; @@ -261,4 +351,66 @@ mod tests { let without_constraint = PyProjectToml::new(pyproject_file).unwrap(); assert!(!without_constraint.warn_missing_maturin_version()); } + + #[test] + fn deserialize_include_exclude() { + let single = r#"include = ["single"]"#; + assert_eq!( + toml_edit::easy::from_str::(single) + .unwrap() + .include, + Some(vec![GlobPattern::Path("single".to_string())]) + ); + + let multiple = r#"include = ["one", "two"]"#; + assert_eq!( + toml_edit::easy::from_str::(multiple) + .unwrap() + .include, + Some(vec![ + GlobPattern::Path("one".to_string()), + GlobPattern::Path("two".to_string()) + ]) + ); + + let single_format = r#"include = [{path = "path", format="sdist"}]"#; + assert_eq!( + toml_edit::easy::from_str::(single_format) + .unwrap() + .include, + Some(vec![GlobPattern::WithFormat { + path: "path".to_string(), + format: Formats::Single(Format::Sdist) + },]) + ); + + let multiple_formats = r#"include = [{path = "path", format=["sdist", "wheel"]}]"#; + assert_eq!( + toml_edit::easy::from_str::(multiple_formats) + .unwrap() + .include, + Some(vec![GlobPattern::WithFormat { + path: "path".to_string(), + format: Formats::Multiple(vec![Format::Sdist, Format::Wheel]) + },]) + ); + + let mixed = r#"include = ["one", {path = "two", format="sdist"}, {path = "three", format=["sdist", "wheel"]}]"#; + assert_eq!( + toml_edit::easy::from_str::(mixed) + .unwrap() + .include, + Some(vec![ + GlobPattern::Path("one".to_string()), + GlobPattern::WithFormat { + path: "two".to_string(), + format: Formats::Single(Format::Sdist), + }, + GlobPattern::WithFormat { + path: "three".to_string(), + format: Formats::Multiple(vec![Format::Sdist, Format::Wheel]) + } + ]) + ); + } } diff --git a/src/source_distribution.rs b/src/source_distribution.rs index 909d1d27e..6bb80d707 100644 --- a/src/source_distribution.rs +++ b/src/source_distribution.rs @@ -1,9 +1,10 @@ use crate::module_writer::{add_data, ModuleWriter}; use crate::polyfill::MetadataCommandExt; -use crate::{BuildContext, PyProjectToml, SDistWriter}; +use crate::{pyproject_toml::Format, BuildContext, PyProjectToml, SDistWriter}; use anyhow::{bail, Context, Result}; use cargo_metadata::{Metadata, MetadataCommand}; use fs_err as fs; +use ignore::overrides::Override; use normpath::PathExt as _; use std::collections::HashMap; use std::path::{Path, PathBuf}; @@ -411,6 +412,7 @@ fn find_path_deps(cargo_metadata: &Metadata) -> Result> pub fn source_distribution( build_context: &BuildContext, pyproject: &PyProjectToml, + excludes: Option, ) -> Result { let metadata21 = &build_context.metadata21; let manifest_path = &build_context.manifest_path; @@ -427,7 +429,7 @@ pub fn source_distribution( let known_path_deps = find_path_deps(&build_context.cargo_metadata)?; - let mut writer = SDistWriter::new(&build_context.out, metadata21)?; + let mut writer = SDistWriter::new(&build_context.out, metadata21, excludes)?; let root_dir = PathBuf::from(format!( "{}-{}", &metadata21.get_distribution_escaped(), @@ -551,20 +553,38 @@ pub fn source_distribution( } } + let mut include = |pattern| -> Result<()> { + println!("📦 Including files matching \"{}\"", pattern); + for source in glob::glob(&pyproject_dir.join(pattern).to_string_lossy()) + .expect("No files found for pattern") + .filter_map(Result::ok) + { + let target = root_dir.join(source.strip_prefix(pyproject_dir).unwrap()); + if source.is_dir() { + writer.add_directory(target)?; + } else { + writer.add_file(target, source)?; + } + } + Ok(()) + }; + + #[allow(deprecated)] if let Some(include_targets) = pyproject.sdist_include() { + eprintln!( + "⚠️ Warning: `[tool.maturin.sdist-include]` is deprecated, please use `[tool.maturin.include]`" + ); for pattern in include_targets { - println!("📦 Including files matching \"{}\"", pattern); - for source in glob::glob(&pyproject_dir.join(pattern).to_string_lossy()) - .expect("No files found for pattern") - .filter_map(Result::ok) - { - let target = root_dir.join(source.strip_prefix(pyproject_dir).unwrap()); - if source.is_dir() { - writer.add_directory(target)?; - } else { - writer.add_file(target, source)?; - } - } + include(pattern.as_str())?; + } + } + + if let Some(glob_patterns) = pyproject.include() { + for pattern in glob_patterns + .iter() + .filter_map(|glob_pattern| glob_pattern.targets(Format::Sdist)) + { + include(pattern)?; } } diff --git a/test-crates/pyo3-mixed-include-exclude/Cargo.lock b/test-crates/pyo3-mixed-include-exclude/Cargo.lock new file mode 100644 index 000000000..7b0a64772 --- /dev/null +++ b/test-crates/pyo3-mixed-include-exclude/Cargo.lock @@ -0,0 +1,289 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "indoc" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adab1eaa3408fb7f0c777a73e7465fd5656136fc93b670eb6df3c88c2c1344e3" + +[[package]] +name = "libc" +version = "0.2.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "proc-macro2" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pyo3" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "268be0c73583c183f2b14052337465768c07726936a260f480f0857cb95ba543" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "memoffset", + "parking_lot", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28fcd1e73f06ec85bf3280c48c67e731d8290ad3d730f8be9dc07946923005c8" +dependencies = [ + "once_cell", + "python3-dll-a", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f6cb136e222e49115b3c51c32792886defbfb0adead26a688142b346a0b9ffc" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94144a1266e236b1c932682136dc35a9dee8d3589728f68130c7c3861ef96b28" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8df9be978a2d2f0cdebabb03206ed73b11314701a5bfe71b0d753b81997777f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pyo3-mixed-include-exclude" +version = "2.1.3" +dependencies = [ + "pyo3", +] + +[[package]] +name = "python3-dll-a" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a915bd72824962bf190bbd3e8a044cccb695d1409f73ff5493712eda5136c7a8" +dependencies = [ + "cc", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "syn" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-lexicon" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02424087780c9b71cc96799eaeddff35af2bc513278cda5c99fc1f5d026d3c1" + +[[package]] +name = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + +[[package]] +name = "unindent" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58ee9362deb4a96cef4d437d1ad49cffc9b9e92d202b6995674e928ce684f112" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" diff --git a/test-crates/pyo3-mixed-include-exclude/Cargo.toml b/test-crates/pyo3-mixed-include-exclude/Cargo.toml new file mode 100644 index 000000000..82f69433c --- /dev/null +++ b/test-crates/pyo3-mixed-include-exclude/Cargo.toml @@ -0,0 +1,16 @@ +[package] +authors = [] +name = "pyo3-mixed-include-exclude" +version = "2.1.3" +description = "Implements a dummy function combining rust and python" +edition = "2021" + +[dependencies] +pyo3 = { version = "0.17.3", features = [ + "extension-module", + "generate-import-lib", +] } + +[lib] +name = "pyo3_mixed_include_exclude" +crate-type = ["cdylib"] diff --git a/test-crates/pyo3-mixed-include-exclude/README.md b/test-crates/pyo3-mixed-include-exclude/README.md new file mode 100644 index 000000000..ea9cc57d7 --- /dev/null +++ b/test-crates/pyo3-mixed-include-exclude/README.md @@ -0,0 +1,30 @@ +# pyo3-mixed-include-exclude + +A package for testing maturin with a mixed pyo3/python project with include and exclude configurations. + +## Usage + +```bash +pip install . +``` + +```python +import pyo3_mixed_include_exclude +assert pyo3_mixed_include_exclude.get_42() == 42 +``` + +## Testing + +Install tox: + +```bash +pip install tox +``` + +Run it: + +```bash +tox +``` + +The tests are in `test_pyo3_mixed_include_exclude.py`, while the configuration is in tox.ini diff --git a/test-crates/pyo3-mixed-include-exclude/check_installed/check_installed.py b/test-crates/pyo3-mixed-include-exclude/check_installed/check_installed.py new file mode 100755 index 000000000..d135ea2b2 --- /dev/null +++ b/test-crates/pyo3-mixed-include-exclude/check_installed/check_installed.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python3 + +import pyo3_mixed_include_exclude + +assert pyo3_mixed_include_exclude.get_42() == 42 + +print("SUCCESS") diff --git a/test-crates/pyo3-mixed-include-exclude/pyo3_mixed_include_exclude/.gitignore b/test-crates/pyo3-mixed-include-exclude/pyo3_mixed_include_exclude/.gitignore new file mode 100644 index 000000000..d3a83a04c --- /dev/null +++ b/test-crates/pyo3-mixed-include-exclude/pyo3_mixed_include_exclude/.gitignore @@ -0,0 +1 @@ +include_this_file diff --git a/test-crates/pyo3-mixed-include-exclude/pyo3_mixed_include_exclude/__init__.py b/test-crates/pyo3-mixed-include-exclude/pyo3_mixed_include_exclude/__init__.py new file mode 100644 index 000000000..2114fe5d3 --- /dev/null +++ b/test-crates/pyo3-mixed-include-exclude/pyo3_mixed_include_exclude/__init__.py @@ -0,0 +1,6 @@ +from .python_module.double import double +from .pyo3_mixed_include_exclude import get_21 + + +def get_42() -> int: + return double(get_21) diff --git a/test-crates/pyo3-mixed-include-exclude/pyo3_mixed_include_exclude/exclude_this_file b/test-crates/pyo3-mixed-include-exclude/pyo3_mixed_include_exclude/exclude_this_file new file mode 100644 index 000000000..e69de29bb diff --git a/test-crates/pyo3-mixed-include-exclude/pyo3_mixed_include_exclude/include_this_file b/test-crates/pyo3-mixed-include-exclude/pyo3_mixed_include_exclude/include_this_file new file mode 100644 index 000000000..e69de29bb diff --git a/test-crates/pyo3-mixed-include-exclude/pyo3_mixed_include_exclude/python_module/__init__.py b/test-crates/pyo3-mixed-include-exclude/pyo3_mixed_include_exclude/python_module/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test-crates/pyo3-mixed-include-exclude/pyo3_mixed_include_exclude/python_module/double.py b/test-crates/pyo3-mixed-include-exclude/pyo3_mixed_include_exclude/python_module/double.py new file mode 100644 index 000000000..2eed18d52 --- /dev/null +++ b/test-crates/pyo3-mixed-include-exclude/pyo3_mixed_include_exclude/python_module/double.py @@ -0,0 +1,5 @@ +from typing import Callable + + +def double(fn: Callable[[], int]) -> int: + return 2 * fn() diff --git a/test-crates/pyo3-mixed-include-exclude/pyproject.toml b/test-crates/pyo3-mixed-include-exclude/pyproject.toml new file mode 100644 index 000000000..6de4ebe79 --- /dev/null +++ b/test-crates/pyo3-mixed-include-exclude/pyproject.toml @@ -0,0 +1,24 @@ +[build-system] +requires = ["maturin>=0.13,<0.14"] +build-backend = "maturin" + +[project] +name = "pyo3-mixed-include-exclude" +classifiers = ["Programming Language :: Python", "Programming Language :: Rust"] +requires-python = ">=3.7" + +[project.scripts] +get_42 = "pyo3_mixed_include_exclude:get_42" + +[tool.maturin] +include = [ + "pyo3_mixed_include_exclude/include_this_file", + "missing", + "README.md", +] +exclude = [ + "pyo3_mixed_include_exclude/exclude_this_file", + "pyo3_mixed_include_exclude/.gitignore", + "tests/**/*", + "unused", +] diff --git a/test-crates/pyo3-mixed-include-exclude/src/lib.rs b/test-crates/pyo3-mixed-include-exclude/src/lib.rs new file mode 100644 index 000000000..142e0fce3 --- /dev/null +++ b/test-crates/pyo3-mixed-include-exclude/src/lib.rs @@ -0,0 +1,13 @@ +use pyo3::prelude::*; + +#[pyfunction] +fn get_21() -> usize { + 21 +} + +#[pymodule] +fn pyo3_mixed_include_exclude(_py: Python, m: &PyModule) -> PyResult<()> { + m.add_wrapped(wrap_pyfunction!(get_21))?; + + Ok(()) +} diff --git a/test-crates/pyo3-mixed-include-exclude/tests/test_pyo3_mixed_include_exclude.py b/test-crates/pyo3-mixed-include-exclude/tests/test_pyo3_mixed_include_exclude.py new file mode 100644 index 000000000..cbb54868d --- /dev/null +++ b/test-crates/pyo3-mixed-include-exclude/tests/test_pyo3_mixed_include_exclude.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python3 + +import pyo3_mixed_include_exclude + + +def test_get_42(): + assert pyo3_mixed_include_exclude.get_42() == 42 diff --git a/test-crates/pyo3-mixed-include-exclude/tox.ini b/test-crates/pyo3-mixed-include-exclude/tox.ini new file mode 100644 index 000000000..421774193 --- /dev/null +++ b/test-crates/pyo3-mixed-include-exclude/tox.ini @@ -0,0 +1,7 @@ +[tox] +envlist = py36,py37,py38 +isolated_build = True + +[testenv] +deps = pytest +commands = pytest tests/ diff --git a/tests/common/other.rs b/tests/common/other.rs index cbdb7dc01..9ec5594af 100644 --- a/tests/common/other.rs +++ b/tests/common/other.rs @@ -1,13 +1,15 @@ use anyhow::{Context, Result}; use clap::Parser; use flate2::read::GzDecoder; -use maturin::{BuildOptions, CargoOptions}; +use maturin::{BuildOptions, CargoOptions, PlatformTag}; use pretty_assertions::assert_eq; use std::collections::BTreeSet; +use std::fs::File; use std::io::Read; use std::iter::FromIterator; use std::path::{Path, PathBuf}; use tar::Archive; +use zip::ZipArchive; /// Tries to compile a sample crate (pyo3-pure) for musl, /// given that rustup and the the musl target are installed @@ -169,6 +171,50 @@ pub fn test_source_distribution( Ok(()) } +pub fn check_wheel_files( + package: impl AsRef, + expected_files: Vec<&str>, + unique_name: &str, +) -> Result<()> { + let manifest_path = package.as_ref().join("Cargo.toml"); + let wheel_directory = Path::new("test-crates").join("wheels").join(unique_name); + + let build_options = BuildOptions { + out: Some(wheel_directory), + cargo: CargoOptions { + manifest_path: Some(manifest_path), + quiet: true, + target_dir: Some(PathBuf::from(format!( + "test-crates/targets/{}", + unique_name + ))), + ..Default::default() + }, + platform_tag: vec![PlatformTag::Linux], + ..Default::default() + }; + + let build_context = build_options.into_build_context(false, false, false)?; + let wheels = build_context + .build_wheels() + .context("Failed to build wheels")?; + assert!(!wheels.is_empty()); + let (wheel_path, _) = &wheels[0]; + + let wheel = ZipArchive::new(File::open(wheel_path)?)?; + let drop_platform_specific_files = |file: &&str| -> bool { + !matches!(Path::new(file).extension(), Some(ext) if ext == "pyc" || ext == "pyd" || ext == "so") + }; + assert_eq!( + wheel + .file_names() + .filter(drop_platform_specific_files) + .collect::>(), + expected_files.into_iter().collect::>() + ); + Ok(()) +} + pub fn abi3_python_interpreter_args() -> Result<()> { // Case 1: maturin build without `-i`, should work let options = BuildOptions::try_parse_from(vec![ diff --git a/tests/run.rs b/tests/run.rs index e2c61054a..8ed3090a9 100644 --- a/tests/run.rs +++ b/tests/run.rs @@ -44,6 +44,16 @@ fn develop_pyo3_mixed() { )); } +#[test] +fn develop_pyo3_mixed_include_exclude() { + handle_result(develop::test_develop( + "test-crates/pyo3-mixed-include-exclude", + None, + "develop-pyo3-mixed-include-exclude", + false, + )); +} + #[test] fn develop_pyo3_mixed_submodule() { handle_result(develop::test_develop( @@ -193,6 +203,17 @@ fn integration_pyo3_mixed() { )); } +#[test] +fn integration_pyo3_mixed_include_exclude() { + handle_result(integration::test_integration( + "test-crates/pyo3-mixed-include-exclude", + None, + "integration-pyo3-mixed-include-exclude", + false, + None, + )); +} + #[test] fn integration_pyo3_mixed_submodule() { handle_result(integration::test_integration( @@ -463,6 +484,51 @@ fn pyo3_mixed_src_layout_sdist() { )) } +#[test] +fn pyo3_mixed_include_exclude_sdist() { + handle_result(other::test_source_distribution( + "test-crates/pyo3-mixed-include-exclude", + vec![ + // "pyo3_mixed_include_exclude-2.1.3/.gitignore", // excluded + "pyo3_mixed_include_exclude-2.1.3/Cargo.lock", + "pyo3_mixed_include_exclude-2.1.3/Cargo.toml", + "pyo3_mixed_include_exclude-2.1.3/PKG-INFO", + "pyo3_mixed_include_exclude-2.1.3/README.md", + "pyo3_mixed_include_exclude-2.1.3/check_installed/check_installed.py", + // "pyo3_mixed_include_exclude-2.1.3/pyo3_mixed_include_exclude/exclude_this_file, excluded + "pyo3_mixed_include_exclude-2.1.3/pyo3_mixed_include_exclude/__init__.py", + "pyo3_mixed_include_exclude-2.1.3/pyo3_mixed_include_exclude/include_this_file", // included + "pyo3_mixed_include_exclude-2.1.3/pyo3_mixed_include_exclude/python_module/__init__.py", + "pyo3_mixed_include_exclude-2.1.3/pyo3_mixed_include_exclude/python_module/double.py", + "pyo3_mixed_include_exclude-2.1.3/pyproject.toml", + "pyo3_mixed_include_exclude-2.1.3/src/lib.rs", + // "pyo3_mixed_include_exclude-2.1.3/tests/test_pyo3_mixed_include_exclude.py", excluded + "pyo3_mixed_include_exclude-2.1.3/tox.ini", + ], + None, + "sdist-pyo3-mixed-include-exclude", + )) +} + +#[test] +fn pyo3_mixed_include_exclude_wheel_files() { + handle_result(other::check_wheel_files( + "test-crates/pyo3-mixed-include-exclude", + vec![ + "pyo3_mixed_include_exclude-2.1.3.dist-info/METADATA", + "pyo3_mixed_include_exclude-2.1.3.dist-info/RECORD", + "pyo3_mixed_include_exclude-2.1.3.dist-info/WHEEL", + "pyo3_mixed_include_exclude-2.1.3.dist-info/entry_points.txt", + "pyo3_mixed_include_exclude/__init__.py", + "pyo3_mixed_include_exclude/include_this_file", + "pyo3_mixed_include_exclude/python_module/__init__.py", + "pyo3_mixed_include_exclude/python_module/double.py", + "README.md", + ], + "wheel-files-pyo3-mixed-include-exclude", + )) +} + #[test] fn workspace_with_path_dep_sdist() { handle_result(other::test_source_distribution(