diff --git a/Cargo.toml b/Cargo.toml index 520fefb3e..e6119bcef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -82,7 +82,8 @@ normpath = "1.1.1" path-slash = "0.2.1" pep440_rs = { version = "0.5.0", features = ["serde", "tracing"] } pep508_rs = { version = "0.4.2", features = ["serde", "tracing"] } -time = "0.3.34" +time = "0.3.17" +url = "2.5.0" unicode-xid = { version = "0.2.4", optional = true } # cli @@ -127,8 +128,7 @@ rustls-pemfile = { version = "2.1.0", optional = true } keyring = { version = "2.3.2", default-features = false, features = [ "linux-no-secret-service", ], optional = true } -wild = { version = "2.2.1", optional = true } -url = { version = "2.3.0", optional = true } +wild = { version = "2.1.0", optional = true } [dev-dependencies] expect-test = "1.4.1" @@ -154,10 +154,10 @@ upload = [ "configparser", "bytesize", "dialoguer/password", - "url", "wild", "dep:dirs", ] + # keyring doesn't support *BSD so it's not enabled in `full` by default password-storage = ["upload", "keyring"] diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index f7041481f..696d8bdaa 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -11,7 +11,7 @@ - [Python Metadata](./metadata.md) - [Configuration](./config.md) - [Environment Variables](./environment-variables.md) -- [Local Development](./develop.md) +- [Local Development](./local_development.md) - [Distribution](./distribution.md) - [Sphinx Integration](./sphinx.md) diff --git a/guide/src/develop.md b/guide/src/local_development.md similarity index 87% rename from guide/src/develop.md rename to guide/src/local_development.md index 8ee68b754..1f80890b2 100644 --- a/guide/src/develop.md +++ b/guide/src/local_development.md @@ -117,15 +117,26 @@ requires = ["maturin>=1.0,<2.0"] build-backend = "maturin" ``` -Editable installs right now is only useful in mixed Rust/Python project so you -don't have to recompile and reinstall when only Python source code changes. For -example when using pip you can make an editable installation with +Editable installs can be used with mixed Rust/Python projects so you +don't have to recompile and reinstall when only Python source code changes. +They can also be used with mixed and pure projects together with the +import hook so that recompilation/re-installation occurs automatically +when Python or Rust source code changes. + +To install a package in editable mode with pip: ```bash +cd my-project pip install -e . ``` +or +```bash +cd my-project +maturin develop +``` -Then Python source code changes will take effect immediately. +Then Python source code changes will take effect immediately because the interpreter looks +for the modules directly in the project source tree. ## Import Hook diff --git a/src/develop.rs b/src/develop.rs index f6ed88a4f..aed9d2c28 100644 --- a/src/develop.rs +++ b/src/develop.rs @@ -1,5 +1,6 @@ use crate::build_options::CargoOptions; use crate::target::Arch; +use crate::BuildContext; use crate::BuildOptions; use crate::PlatformTag; use crate::PythonInterpreter; @@ -7,10 +8,13 @@ use crate::Target; use anyhow::{anyhow, bail, Context, Result}; use cargo_options::heading; use pep508_rs::{MarkerExpression, MarkerOperator, MarkerTree, MarkerValue}; +use regex::Regex; +use std::fs; use std::path::Path; use std::path::PathBuf; use std::process::Command; use tempfile::TempDir; +use url::Url; /// Install the crate as module in the current virtualenv #[derive(Debug, clap::Parser)] @@ -72,6 +76,143 @@ fn make_pip_command(python_path: &Path, pip_path: Option<&Path>) -> Command { } } +fn install_dependencies( + build_context: &BuildContext, + extras: &[String], + interpreter: &PythonInterpreter, + pip_path: Option<&Path>, +) -> Result<()> { + if !build_context.metadata21.requires_dist.is_empty() { + let mut args = vec!["install".to_string()]; + args.extend(build_context.metadata21.requires_dist.iter().map(|x| { + let mut pkg = x.clone(); + // Remove extra marker to make it installable with pip + // Keep in sync with `Metadata21::merge_pyproject_toml()`! + for extra in extras { + pkg.marker = pkg.marker.and_then(|marker| -> Option { + match marker.clone() { + MarkerTree::Expression(MarkerExpression { + l_value: MarkerValue::Extra, + operator: MarkerOperator::Equal, + r_value: MarkerValue::QuotedString(extra_value), + }) if &extra_value == extra => None, + MarkerTree::And(and) => match &*and { + [existing, MarkerTree::Expression(MarkerExpression { + l_value: MarkerValue::Extra, + operator: MarkerOperator::Equal, + r_value: MarkerValue::QuotedString(extra_value), + })] if extra_value == extra => Some(existing.clone()), + _ => Some(marker), + }, + _ => Some(marker), + } + }); + } + pkg.to_string() + })); + let status = make_pip_command(&interpreter.executable, pip_path) + .args(&args) + .status() + .context("Failed to run pip install")?; + if !status.success() { + bail!(r#"pip install finished with "{}""#, status) + } + } + Ok(()) +} + +fn pip_install_wheel( + build_context: &BuildContext, + python: &Path, + venv_dir: &Path, + pip_path: Option<&Path>, + wheel_filename: &Path, +) -> Result<()> { + let mut pip_cmd = make_pip_command(python, pip_path); + let output = pip_cmd + .args(["install", "--no-deps", "--force-reinstall"]) + .arg(dunce::simplified(wheel_filename)) + .output() + .context(format!( + "pip install failed (ran {:?} with {:?})", + pip_cmd.get_program(), + &pip_cmd.get_args().collect::>(), + ))?; + if !output.status.success() { + bail!( + "pip install in {} failed running {:?}: {}\n--- Stdout:\n{}\n--- Stderr:\n{}\n---\n", + venv_dir.display(), + &pip_cmd.get_args().collect::>(), + output.status, + String::from_utf8_lossy(&output.stdout).trim(), + String::from_utf8_lossy(&output.stderr).trim(), + ); + } + if !output.stderr.is_empty() { + eprintln!( + "⚠️ Warning: pip raised a warning running {:?}:\n{}", + &pip_cmd.get_args().collect::>(), + String::from_utf8_lossy(&output.stderr).trim(), + ); + } + fix_direct_url(build_context, python, pip_path)?; + Ok(()) +} + +/// Each editable-installed python package has a direct_url.json file that includes a file:// URL +/// indicating the location of the source code of that project. The maturin import hook uses this +/// URL to locate and rebuild editable-installed projects. +/// +/// When a maturin package is installed using `pip install -e`, pip takes care of writing the +/// correct URL, however when a maturin package is installed with `maturin develop`, the URL is +/// set to the path to the temporary wheel file created during installation. +fn fix_direct_url( + build_context: &BuildContext, + python: &Path, + pip_path: Option<&Path>, +) -> Result<()> { + println!("✏️ Setting installed package as editable"); + let mut pip_cmd = make_pip_command(python, pip_path); + let output = pip_cmd + .args(["show", "--files"]) + .arg(&build_context.metadata21.name) + .output() + .context(format!( + "pip show failed (ran {:?} with {:?})", + pip_cmd.get_program(), + &pip_cmd.get_args().collect::>(), + ))?; + if let Some(direct_url_path) = parse_direct_url_path(&String::from_utf8_lossy(&output.stdout))? + { + let project_dir = build_context + .pyproject_toml_path + .parent() + .ok_or_else(|| anyhow!("failed to get project directory"))?; + let uri = Url::from_file_path(project_dir) + .map_err(|_| anyhow!("failed to convert project directory to file URL"))?; + let content = format!("{{\"dir_info\": {{\"editable\": true}}, \"url\": \"{uri}\"}}"); + fs::write(direct_url_path, content)?; + } + Ok(()) +} + +fn parse_direct_url_path(pip_show_output: &str) -> Result> { + if let Some(Some(location)) = Regex::new(r"Location: ([^\r\n]*)")? + .captures(pip_show_output) + .map(|c| c.get(1)) + { + if let Some(Some(direct_url_path)) = Regex::new(r" (.*direct_url.json)")? + .captures(pip_show_output) + .map(|c| c.get(1)) + { + return Ok(Some( + PathBuf::from(location.as_str()).join(direct_url_path.as_str()), + )); + } + } + Ok(None) +} + /// Installs a crate by compiling it and copying the shared library to site-packages. /// Also adds the dist-info directory to make sure pip and other tools detect the library /// @@ -137,74 +278,18 @@ pub fn develop(develop_options: DevelopOptions, venv_dir: &Path) -> Result<()> { || anyhow!("Expected `python` to be a python interpreter inside a virtualenv ಠ_ಠ"), )?; - // Install dependencies - if !build_context.metadata21.requires_dist.is_empty() { - let mut args = vec!["install".to_string()]; - args.extend(build_context.metadata21.requires_dist.iter().map(|x| { - let mut pkg = x.clone(); - // Remove extra marker to make it installable with pip - // Keep in sync with `Metadata21::merge_pyproject_toml()`! - for extra in &extras { - pkg.marker = pkg.marker.and_then(|marker| -> Option { - match marker.clone() { - MarkerTree::Expression(MarkerExpression { - l_value: MarkerValue::Extra, - operator: MarkerOperator::Equal, - r_value: MarkerValue::QuotedString(extra_value), - }) if &extra_value == extra => None, - MarkerTree::And(and) => match &*and { - [existing, MarkerTree::Expression(MarkerExpression { - l_value: MarkerValue::Extra, - operator: MarkerOperator::Equal, - r_value: MarkerValue::QuotedString(extra_value), - })] if extra_value == extra => Some(existing.clone()), - _ => Some(marker), - }, - _ => Some(marker), - } - }); - } - pkg.to_string() - })); - let status = make_pip_command(&interpreter.executable, pip_path.as_deref()) - .args(&args) - .status() - .context("Failed to run pip install")?; - if !status.success() { - bail!(r#"pip install finished with "{}""#, status) - } - } + install_dependencies(&build_context, &extras, &interpreter, pip_path.as_deref())?; let wheels = build_context.build_wheels()?; if !skip_install { for (filename, _supported_version) in wheels.iter() { - let mut pip_cmd = make_pip_command(&python, pip_path.as_deref()); - let output = pip_cmd - .args(["install", "--no-deps", "--force-reinstall"]) - .arg(dunce::simplified(filename)) - .output() - .context(format!( - "pip install failed (ran {:?} with {:?})", - pip_cmd.get_program(), - &pip_cmd.get_args().collect::>(), - ))?; - if !output.status.success() { - bail!( - "pip install in {} failed running {:?}: {}\n--- Stdout:\n{}\n--- Stderr:\n{}\n---\n", - venv_dir.display(), - &pip_cmd.get_args().collect::>(), - output.status, - String::from_utf8_lossy(&output.stdout).trim(), - String::from_utf8_lossy(&output.stderr).trim(), - ); - } - if !output.stderr.is_empty() { - eprintln!( - "⚠️ Warning: pip raised a warning running {:?}:\n{}", - &pip_cmd.get_args().collect::>(), - String::from_utf8_lossy(&output.stderr).trim(), - ); - } + pip_install_wheel( + &build_context, + &python, + venv_dir, + pip_path.as_deref(), + filename, + )?; eprintln!( "🛠 Installed {}-{}", build_context.metadata21.name, build_context.metadata21.version @@ -214,3 +299,79 @@ pub fn develop(develop_options: DevelopOptions, venv_dir: &Path) -> Result<()> { Ok(()) } + +#[cfg(test)] +mod test { + use std::path::PathBuf; + + use super::parse_direct_url_path; + + #[test] + #[cfg(not(target_os = "windows"))] + fn test_parse_direct_url() { + let example_with_direct_url = "\ +Name: my-project +Version: 0.1.0 +Location: /foo bar/venv/lib/pythonABC/site-packages +Editable project location: /tmp/temporary.whl +Files: + my_project-0.1.0+abc123de.dist-info/INSTALLER + my_project-0.1.0+abc123de.dist-info/METADATA + my_project-0.1.0+abc123de.dist-info/RECORD + my_project-0.1.0+abc123de.dist-info/REQUESTED + my_project-0.1.0+abc123de.dist-info/WHEEL + my_project-0.1.0+abc123de.dist-info/direct_url.json + my_project-0.1.0+abc123de.dist-info/entry_points.txt + my_project.pth +"; + let expected_path = PathBuf::from("/foo bar/venv/lib/pythonABC/site-packages/my_project-0.1.0+abc123de.dist-info/direct_url.json"); + assert_eq!( + parse_direct_url_path(example_with_direct_url).unwrap(), + Some(expected_path) + ); + + let example_without_direct_url = "\ +Name: my-project +Version: 0.1.0 +Location: /foo bar/venv/lib/pythonABC/site-packages +Files: + my_project-0.1.0+abc123de.dist-info/INSTALLER + my_project-0.1.0+abc123de.dist-info/METADATA + my_project-0.1.0+abc123de.dist-info/RECORD + my_project-0.1.0+abc123de.dist-info/REQUESTED + my_project-0.1.0+abc123de.dist-info/WHEEL + my_project-0.1.0+abc123de.dist-info/entry_points.txt + my_project.pth +"; + + assert_eq!( + parse_direct_url_path(example_without_direct_url).unwrap(), + None + ); + } + + #[test] + #[cfg(target_os = "windows")] + fn test_parse_direct_url_windows() { + let example_with_direct_url_windows = "\ +Name: my-project\r +Version: 0.1.0\r +Location: C:\\foo bar\\venv\\Lib\\site-packages\r +Files:\r + my_project-0.1.0+abc123de.dist-info\\INSTALLER\r + my_project-0.1.0+abc123de.dist-info\\METADATA\r + my_project-0.1.0+abc123de.dist-info\\RECORD\r + my_project-0.1.0+abc123de.dist-info\\REQUESTED\r + my_project-0.1.0+abc123de.dist-info\\WHEEL\r + my_project-0.1.0+abc123de.dist-info\\direct_url.json\r + my_project-0.1.0+abc123de.dist-info\\entry_points.txt\r + my_project.pth\r +"; + + let expected_path = PathBuf::from("C:\\foo bar\\venv\\Lib\\site-packages\\my_project-0.1.0+abc123de.dist-info\\direct_url.json"); + assert_eq!( + parse_direct_url_path(example_with_direct_url_windows).unwrap(), + Some(expected_path) + ); + } +} diff --git a/test-crates/lib_with_disallowed_lib/Cargo.lock b/test-crates/lib_with_disallowed_lib/Cargo.lock index 1f66fdb7f..ff33cdf2b 100644 --- a/test-crates/lib_with_disallowed_lib/Cargo.lock +++ b/test-crates/lib_with_disallowed_lib/Cargo.lock @@ -29,11 +29,17 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "indoc" -version = "1.0.9" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" +checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" [[package]] name = "lib_with_disallowed_lib" @@ -125,9 +131,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.19.2" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e681a6cfdc4adcc93b4d3cf993749a4552018ee0a9b65fc0ccfad74352c72a38" +checksum = "9a89dc7a5850d0e983be1ec2a463a171d20990487c3cfcd68b5363f1ee3d6fe0" dependencies = [ "cfg-if", "indoc", @@ -142,9 +148,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.19.2" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076c73d0bc438f7a4ef6fdd0c3bb4732149136abd952b110ac93e4edb13a6ba5" +checksum = "07426f0d8fe5a601f26293f300afd1a7b1ed5e78b2a705870c5f30893c5163be" dependencies = [ "once_cell", "target-lexicon", @@ -152,9 +158,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.19.2" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e53cee42e77ebe256066ba8aa77eff722b3bb91f3419177cf4cd0f304d3284d9" +checksum = "dbb7dec17e17766b46bca4f1a4215a85006b4c2ecde122076c562dd058da6cf1" dependencies = [ "libc", "pyo3-build-config", @@ -162,9 +168,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.19.2" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfeb4c99597e136528c6dd7d5e3de5434d1ceaf487436a3f03b2d56b6fc9efd1" +checksum = "05f738b4e40d50b5711957f142878cfa0f28e054aa0ebdfc3fd137a843f74ed3" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -174,10 +180,11 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.19.2" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "947dc12175c254889edc0c02e399476c2f652b4b9ebd123aa655c224de259536" +checksum = "0fc910d4851847827daf9d6cdd4a823fbdaab5b8818325c5e97a86da79e8881f" dependencies = [ + "heck", "proc-macro2", "quote", "syn", @@ -215,9 +222,9 @@ checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" [[package]] name = "syn" -version = "1.0.109" +version = "2.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2" dependencies = [ "proc-macro2", "quote", @@ -238,9 +245,9 @@ checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "unindent" -version = "0.1.11" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" +checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" [[package]] name = "vcpkg" diff --git a/test-crates/license-test/check_installed/check_installed.py b/test-crates/license-test/check_installed/check_installed.py index 286755e90..a49bb636a 100644 --- a/test-crates/license-test/check_installed/check_installed.py +++ b/test-crates/license-test/check_installed/check_installed.py @@ -2,7 +2,7 @@ def main(): - output = check_output(["hello-world"]).decode("utf-8").strip() + output = check_output(["license-test"]).decode("utf-8").strip() if not output == "Hello, world!": raise Exception(output) print("SUCCESS") diff --git a/test-crates/pyo3-mixed-py-subdir/README.md b/test-crates/pyo3-mixed-py-subdir/README.md index 038375070..a148582ec 100644 --- a/test-crates/pyo3-mixed-py-subdir/README.md +++ b/test-crates/pyo3-mixed-py-subdir/README.md @@ -1,6 +1,6 @@ # pyo3-mixed -A package for testing maturin with a mixed pyo3/python project. +A package for testing maturin with a mixed pyo3/python project and a non-default package name. ## Usage @@ -9,8 +9,8 @@ pip install . ``` ```python -import pyo3_mixed -assert pyo3_mixed.get_42() == 42 +import pyo3_mixed_py_subdir +assert pyo3_mixed_py_subdir.get_42() == 42 ``` ## Testing diff --git a/test-crates/pyo3-mixed-with-path-dep/Cargo.lock b/test-crates/pyo3-mixed-with-path-dep/Cargo.lock new file mode 100644 index 000000000..278ea1cd0 --- /dev/null +++ b/test-crates/pyo3-mixed-with-path-dep/Cargo.lock @@ -0,0 +1,311 @@ +# 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.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c6b2562119bf28c3439f7f02db99faf0aa1a8cdfe5772a2ee155d32227239f0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "indoc" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "lock_api" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[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.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pyo3" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a89dc7a5850d0e983be1ec2a463a171d20990487c3cfcd68b5363f1ee3d6fe0" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "memoffset", + "parking_lot", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07426f0d8fe5a601f26293f300afd1a7b1ed5e78b2a705870c5f30893c5163be" +dependencies = [ + "once_cell", + "python3-dll-a", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb7dec17e17766b46bca4f1a4215a85006b4c2ecde122076c562dd058da6cf1" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f738b4e40d50b5711957f142878cfa0f28e054aa0ebdfc3fd137a843f74ed3" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc910d4851847827daf9d6cdd4a823fbdaab5b8818325c5e97a86da79e8881f" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pyo3-mixed-with-path-dep" +version = "2.1.3" +dependencies = [ + "pyo3", + "some_path_dep", +] + +[[package]] +name = "python3-dll-a" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f07cd4412be8fa09a721d40007c483981bbe072cd6a21f2e83e04ec8f8343f" +dependencies = [ + "cc", +] + +[[package]] +name = "quote" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "smallvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" + +[[package]] +name = "some_path_dep" +version = "0.1.0" +dependencies = [ + "transitive_path_dep", +] + +[[package]] +name = "syn" +version = "2.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-lexicon" +version = "0.12.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" + +[[package]] +name = "transitive_path_dep" +version = "0.1.0" + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "unindent" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" + +[[package]] +name = "windows-targets" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +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.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" diff --git a/test-crates/pyo3-mixed-with-path-dep/Cargo.toml b/test-crates/pyo3-mixed-with-path-dep/Cargo.toml new file mode 100644 index 000000000..14a4ecbfc --- /dev/null +++ b/test-crates/pyo3-mixed-with-path-dep/Cargo.toml @@ -0,0 +1,13 @@ +[package] +authors = [] +name = "pyo3-mixed-with-path-dep" +version = "2.1.3" +edition = "2021" + +[dependencies] +pyo3 = { version = "0.20.0", features = ["extension-module", "generate-import-lib"] } +some_path_dep = { path = "../some_path_dep" } + +[lib] +name = "pyo3_mixed_with_path_dep" +crate-type = ["cdylib"] diff --git a/test-crates/pyo3-mixed-with-path-dep/README.md b/test-crates/pyo3-mixed-with-path-dep/README.md new file mode 100644 index 000000000..29e76130f --- /dev/null +++ b/test-crates/pyo3-mixed-with-path-dep/README.md @@ -0,0 +1,30 @@ +# pyo3-mixed + +A package for testing maturin with a mixed pyo3/python project. + +## Usage + +```bash +pip install . +``` + +```python +import pyo3_mixed_with_path_dep +assert pyo3_mixed_with_path_dep.get_42() == 42 +``` + +## Testing + +Install tox: + +```bash +pip install tox +``` + +Run it: + +```bash +tox +``` + +The tests are in `tests/test_pyo3_mixed_with_path_dep.py`, while the configuration is in tox.ini diff --git a/test-crates/pyo3-mixed-with-path-dep/check_installed/check_installed.py b/test-crates/pyo3-mixed-with-path-dep/check_installed/check_installed.py new file mode 100755 index 000000000..21d947dfc --- /dev/null +++ b/test-crates/pyo3-mixed-with-path-dep/check_installed/check_installed.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python3 +import pyo3_mixed_with_path_dep + +assert pyo3_mixed_with_path_dep.get_42() == 42, "get_42 did not return 42" + +assert pyo3_mixed_with_path_dep.is_half(21, 42), "21 is not half of 42" +assert not pyo3_mixed_with_path_dep.is_half(21, 73), "21 is half of 63" + +print("SUCCESS") diff --git a/test-crates/pyo3-mixed-with-path-dep/pyo3_mixed_with_path_dep/__init__.py b/test-crates/pyo3-mixed-with-path-dep/pyo3_mixed_with_path_dep/__init__.py new file mode 100644 index 000000000..54a76f703 --- /dev/null +++ b/test-crates/pyo3-mixed-with-path-dep/pyo3_mixed_with_path_dep/__init__.py @@ -0,0 +1,7 @@ +from .pyo3_mixed_with_path_dep import get_21, add_21, is_half + +__all__ = ["get_21", "add_21", "is_half", "get_42"] + + +def get_42() -> int: + return add_21(get_21()) diff --git a/test-crates/pyo3-mixed-with-path-dep/pyproject.toml b/test-crates/pyo3-mixed-with-path-dep/pyproject.toml new file mode 100644 index 000000000..cc54d0ae3 --- /dev/null +++ b/test-crates/pyo3-mixed-with-path-dep/pyproject.toml @@ -0,0 +1,11 @@ +[build-system] +requires = ["maturin>=1.0,<2.0"] +build-backend = "maturin" + +[project] +name = "pyo3-mixed-with-path-dep" +classifiers = [ + "Programming Language :: Python", + "Programming Language :: Rust" +] +requires-python = ">=3.7" diff --git a/test-crates/pyo3-mixed-with-path-dep/src/lib.rs b/test-crates/pyo3-mixed-with-path-dep/src/lib.rs new file mode 100644 index 000000000..1b7194289 --- /dev/null +++ b/test-crates/pyo3-mixed-with-path-dep/src/lib.rs @@ -0,0 +1,27 @@ +use pyo3::prelude::*; +use some_path_dep::{add, is_sum}; + +#[pyfunction] +fn get_21() -> usize { + 21 +} + +#[pyfunction] +fn add_21(num: usize) -> usize { + add(num, get_21()) +} + +#[pyfunction] +fn is_half(a: usize, b: usize) -> bool { + is_sum(a, a, b) +} + + +#[pymodule] +fn pyo3_mixed_with_path_dep(_py: Python, m: &PyModule) -> PyResult<()> { + m.add_wrapped(wrap_pyfunction!(get_21))?; + m.add_wrapped(wrap_pyfunction!(add_21))?; + m.add_wrapped(wrap_pyfunction!(is_half))?; + + Ok(()) +} diff --git a/test-crates/pyo3-mixed-with-path-dep/tests/test_pyo3_mixed_with_path_dep.py b/test-crates/pyo3-mixed-with-path-dep/tests/test_pyo3_mixed_with_path_dep.py new file mode 100644 index 000000000..d77884fe8 --- /dev/null +++ b/test-crates/pyo3-mixed-with-path-dep/tests/test_pyo3_mixed_with_path_dep.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python3 + +import pyo3_mixed_with_path_dep + + +def test_get_42(): + assert pyo3_mixed_with_path_dep.get_42() == 42 diff --git a/test-crates/pyo3-mixed-with-path-dep/tox.ini b/test-crates/pyo3-mixed-with-path-dep/tox.ini new file mode 100644 index 000000000..421774193 --- /dev/null +++ b/test-crates/pyo3-mixed-with-path-dep/tox.ini @@ -0,0 +1,7 @@ +[tox] +envlist = py36,py37,py38 +isolated_build = True + +[testenv] +deps = pytest +commands = pytest tests/ diff --git a/tests/run.rs b/tests/run.rs index f85c00198..dad1c68dd 100644 --- a/tests/run.rs +++ b/tests/run.rs @@ -64,6 +64,16 @@ fn develop_pyo3_mixed_submodule() { )); } +#[test] +fn develop_pyo3_mixed_with_path_dep() { + handle_result(develop::test_develop( + "test-crates/pyo3-mixed-with-path-dep", + None, + "develop-pyo3-mixed-with-path-dep", + false, + )); +} + #[test] fn develop_pyo3_mixed_implicit() { handle_result(develop::test_develop( @@ -239,6 +249,17 @@ fn integration_pyo3_mixed_submodule() { )); } +#[test] +fn integration_pyo3_mixed_with_path_dep() { + handle_result(integration::test_integration( + "test-crates/pyo3-mixed-with-path-dep", + None, + "integration-pyo3-mixed-with-path-dep", + false, + None, + )); +} + #[test] fn integration_pyo3_mixed_implicit() { handle_result(integration::test_integration(