Skip to content

Commit c627d88

Browse files
bors[bot]messense
andauthoredDec 29, 2022
Merge #1378
1378: Add support for packaging multiple pure Python packages r=messense a=messense Implements #1372 (comment) Co-authored-by: messense <[email protected]>
2 parents bb616d2 + ef2b09a commit c627d88

File tree

8 files changed

+94
-36
lines changed

8 files changed

+94
-36
lines changed
 

‎Changelog.md

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
* **Breaking Change**: Remove deprecated `python-source` option in `Cargo.toml` in [#1335](https://github.com/PyO3/maturin/pull/1335)
1313
* **Breaking Change**: Turn `patchelf` version warning into a hard error in [#1335](https://github.com/PyO3/maturin/pull/1335)
1414
* **Breaking Change**: [`uniffi_bindgen` CLI](https://mozilla.github.io/uniffi-rs/tutorial/Prerequisites.html#the-uniffi-bindgen-cli-tool) is required for building `uniffi` bindings wheels in [#1352](https://github.com/PyO3/maturin/pull/1352)
15+
* Add support for packaging multiple pure Python packages in [#1378](https://github.com/PyO3/maturin/pull/1378)
1516

1617
## [0.14.7] - 2022-12-20
1718

‎guide/src/metadata.md

+4
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@ bindings = "pyo3"
117117
compatibility = "manylinux2014"
118118
# Don't check for manylinux compliance
119119
skip-auditwheel = false
120+
# Python source directory
121+
python-source = "src"
122+
# Python packages to include
123+
python-packages = ["foo", "bar"]
120124
# Strip the library for minimum file size
121125
strip = true
122126
# Build artifacts with the specified Cargo profile

‎src/module_writer.rs

+33-18
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ impl WheelWriter {
303303
debug!("Adding {} from {}", target, python_path);
304304
self.add_bytes(target, python_path.as_bytes())?;
305305
} else {
306-
println!("⚠️ source code path contains non-Unicode sequences, editable installs may not work.");
306+
eprintln!("⚠️ source code path contains non-Unicode sequences, editable installs may not work.");
307307
}
308308
}
309309
Ok(())
@@ -1098,31 +1098,46 @@ pub fn write_python_part(
10981098
python_module: impl AsRef<Path>,
10991099
pyproject_toml: Option<&PyProjectToml>,
11001100
) -> Result<()> {
1101-
let python_module = python_module.as_ref();
1102-
for absolute in WalkBuilder::new(python_module).hidden(false).build() {
1103-
let absolute = absolute?.into_path();
1104-
let relative = absolute
1105-
.strip_prefix(python_module.parent().unwrap())
1106-
.unwrap();
1107-
if absolute.is_dir() {
1108-
writer.add_directory(relative)?;
1109-
} else {
1110-
// Ignore native libraries from develop, if any
1111-
if let Some(extension) = relative.extension() {
1112-
if extension.to_string_lossy() == "so" {
1113-
debug!("Ignoring native library {}", relative.display());
1101+
let python_module = python_module.as_ref().to_path_buf();
1102+
let python_dir = python_module.parent().unwrap().to_path_buf();
1103+
let mut python_packages = vec![python_module];
1104+
if let Some(pyproject_toml) = pyproject_toml {
1105+
if let Some(packages) = pyproject_toml.python_packages() {
1106+
for package in packages {
1107+
let package_path = python_dir.join(package);
1108+
if python_packages.iter().any(|p| *p == package_path) {
11141109
continue;
11151110
}
1111+
python_packages.push(package_path);
1112+
}
1113+
}
1114+
}
1115+
1116+
for package in python_packages {
1117+
for absolute in WalkBuilder::new(package).hidden(false).build() {
1118+
let absolute = absolute?.into_path();
1119+
let relative = absolute.strip_prefix(&python_dir).unwrap();
1120+
if absolute.is_dir() {
1121+
writer.add_directory(relative)?;
1122+
} else {
1123+
// Ignore native libraries from develop, if any
1124+
if let Some(extension) = relative.extension() {
1125+
if extension.to_string_lossy() == "so" {
1126+
debug!("Ignoring native library {}", relative.display());
1127+
continue;
1128+
}
1129+
}
1130+
writer
1131+
.add_file(relative, &absolute)
1132+
.context(format!("File to add file from {}", absolute.display()))?;
11161133
}
1117-
writer
1118-
.add_file(relative, &absolute)
1119-
.context(format!("File to add file from {}", absolute.display()))?;
11201134
}
11211135
}
11221136

11231137
// Include additional files
11241138
if let Some(pyproject) = pyproject_toml {
1125-
let pyproject_dir = python_module.parent().unwrap();
1139+
// FIXME: in src-layout pyproject.toml isn't located directly in python dir
1140+
let pyproject_dir = &python_dir;
11261141
if let Some(glob_patterns) = pyproject.include() {
11271142
for pattern in glob_patterns
11281143
.iter()

‎src/project_layout.rs

+5
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ const PYPROJECT_TOML: &str = "pyproject.toml";
1313
/// Whether this project is pure rust or rust mixed with python and whether it has wheel data
1414
#[derive(Clone, Debug, PartialEq, Eq)]
1515
pub struct ProjectLayout {
16+
/// Contains the absolute path to the python source directory
17+
pub python_dir: PathBuf,
1618
/// Contains the canonicalized (i.e. absolute) path to the python part of the project
1719
/// If none, we have a rust crate compiled into a shared library with only some glue python for cffi
1820
/// If some, we have a python package that is extended by a native rust module.
@@ -342,6 +344,7 @@ impl ProjectLayout {
342344
};
343345
debug!(
344346
project_root = %project_root.display(),
347+
python_dir = %python_root.display(),
345348
rust_module = %rust_module.display(),
346349
python_module = %python_module.display(),
347350
extension_name = %extension_name,
@@ -369,13 +372,15 @@ impl ProjectLayout {
369372
println!("🍹 Building a mixed python/rust project");
370373

371374
Ok(ProjectLayout {
375+
python_dir: python_root,
372376
python_module: Some(python_module),
373377
rust_module,
374378
extension_name,
375379
data,
376380
})
377381
} else {
378382
Ok(ProjectLayout {
383+
python_dir: python_root,
379384
python_module: None,
380385
rust_module: project_root.to_path_buf(),
381386
extension_name,

‎src/pyproject_toml.rs

+13
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ pub struct ToolMaturin {
9797
strip: bool,
9898
/// The directory with python module, contains `<module_name>/__init__.py`
9999
python_source: Option<PathBuf>,
100+
/// Python packages to include
101+
python_packages: Option<Vec<String>>,
100102
/// Path to the wheel directory, defaults to `<module_name>.data`
101103
data: Option<PathBuf>,
102104
// Some customizable cargo options
@@ -208,6 +210,12 @@ impl PyProjectToml {
208210
.and_then(|maturin| maturin.python_source.as_deref())
209211
}
210212

213+
/// Returns the value of `[tool.maturin.python-packages]` in pyproject.toml
214+
pub fn python_packages(&self) -> Option<&[String]> {
215+
self.maturin()
216+
.and_then(|maturin| maturin.python_packages.as_deref())
217+
}
218+
211219
/// Returns the value of `[tool.maturin.data]` in pyproject.toml
212220
pub fn data(&self) -> Option<&Path> {
213221
self.maturin().and_then(|maturin| maturin.data.as_deref())
@@ -291,6 +299,7 @@ mod tests {
291299
292300
[tool.maturin]
293301
manylinux = "2010"
302+
python-packages = ["foo", "bar"]
294303
manifest-path = "Cargo.toml"
295304
profile = "dev"
296305
features = ["foo", "bar"]
@@ -317,6 +326,10 @@ mod tests {
317326
maturin.rustc_args,
318327
Some(vec!["-Z".to_string(), "unstable-options".to_string()])
319328
);
329+
assert_eq!(
330+
maturin.python_packages,
331+
Some(vec!["foo".to_string(), "bar".to_string()])
332+
);
320333
}
321334

322335
#[test]

‎src/source_distribution.rs

+34-18
Original file line numberDiff line numberDiff line change
@@ -620,26 +620,42 @@ pub fn source_distribution(
620620

621621
let pyproject_dir = pyproject_toml_path.parent().unwrap();
622622
// Add python source files
623-
if let Some(python_source) = build_context.project_layout.python_module.as_ref() {
624-
for entry in ignore::Walk::new(python_source) {
625-
let source = entry?.into_path();
626-
// Technically, `ignore` crate should handle this,
627-
// but somehow it doesn't on Alpine Linux running in GitHub Actions,
628-
// so we do it manually here.
629-
// See https://github.com/PyO3/maturin/pull/1187#issuecomment-1273987013
630-
if source
631-
.extension()
632-
.map(|ext| ext == "pyc" || ext == "pyd" || ext == "so")
633-
.unwrap_or_default()
634-
{
635-
debug!("Ignoring {}", source.display());
623+
if let Some(python_module) = build_context.project_layout.python_module.as_ref() {
624+
let mut python_packages = vec![python_module.to_path_buf()];
625+
for package in build_context
626+
.pyproject_toml
627+
.as_ref()
628+
.and_then(|toml| toml.python_packages())
629+
.unwrap_or_default()
630+
{
631+
let package_path = build_context.project_layout.python_dir.join(package);
632+
if python_packages.iter().any(|p| *p == package_path) {
636633
continue;
637634
}
638-
let target = root_dir.join(source.strip_prefix(pyproject_dir).unwrap());
639-
if source.is_dir() {
640-
writer.add_directory(target)?;
641-
} else {
642-
writer.add_file(target, &source)?;
635+
python_packages.push(package_path);
636+
}
637+
638+
for package in python_packages {
639+
for entry in ignore::Walk::new(package) {
640+
let source = entry?.into_path();
641+
// Technically, `ignore` crate should handle this,
642+
// but somehow it doesn't on Alpine Linux running in GitHub Actions,
643+
// so we do it manually here.
644+
// See https://github.com/PyO3/maturin/pull/1187#issuecomment-1273987013
645+
if source
646+
.extension()
647+
.map(|ext| ext == "pyc" || ext == "pyd" || ext == "so")
648+
.unwrap_or_default()
649+
{
650+
debug!("Ignoring {}", source.display());
651+
continue;
652+
}
653+
let target = root_dir.join(source.strip_prefix(pyproject_dir).unwrap());
654+
if source.is_dir() {
655+
writer.add_directory(target)?;
656+
} else {
657+
writer.add_file(target, &source)?;
658+
}
643659
}
644660
}
645661
}

‎test-crates/pyo3-mixed-src/pyproject.toml

+3
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,6 @@ requires-python = ">=3.7"
1212

1313
[project.scripts]
1414
get_42 = "pyo3_mixed_src:get_42"
15+
16+
[tool.maturin]
17+
python-packages = ["pyo3_mixed_src", "tests"]

‎tests/run.rs

+1
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,7 @@ fn pyo3_mixed_src_layout_sdist() {
491491
"pyo3_mixed_src-2.1.3/src/pyo3_mixed_src/__init__.py",
492492
"pyo3_mixed_src-2.1.3/src/pyo3_mixed_src/python_module/__init__.py",
493493
"pyo3_mixed_src-2.1.3/src/pyo3_mixed_src/python_module/double.py",
494+
"pyo3_mixed_src-2.1.3/src/tests/test_pyo3_mixed.py",
494495
"pyo3_mixed_src-2.1.3/rust/Cargo.toml",
495496
"pyo3_mixed_src-2.1.3/rust/Cargo.lock",
496497
"pyo3_mixed_src-2.1.3/rust/src/lib.rs",

0 commit comments

Comments
 (0)
Please sign in to comment.