Skip to content

Commit

Permalink
Add path option for Python source.
Browse files Browse the repository at this point in the history
  • Loading branch information
sebpuetz committed Jul 29, 2020
1 parent 523ada7 commit 2cb5c3e
Show file tree
Hide file tree
Showing 15 changed files with 83 additions and 20 deletions.
3 changes: 3 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
include arbitrary files in source distributions ([#296](https://github.com/PyO3/maturin/pull/296)).
* cffi is installed if it's missing and python is running inside a virtualenv.
* Add support for PyO3 `0.12`'s `PYO3_PYTHON` environment variable. [#331](https://github.com/PyO3/maturin/pull/331)
* Enable mixed layouts with Python source in different sub-directories. Either through `tools.maturin.py-src`
in `pyproject.toml` or through the `--py-src` flag on `maturin`.
([#335](https://github.com/PyO3/maturin/pull/335))

## 0.8.0 - 2020-04-03

Expand Down
2 changes: 2 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ my-project
   └── lib.rs
```

You can also specify the directory housing the package through the `--py-src` argument.

maturin will add the native extension as a module in your python folder. When using develop, maturin will copy the native library and for cffi also the glue code to your python folder. You should add those files to your gitignore.

With cffi you can do `from .my_project import lib` and then use `lib.my_native_function`, with pyo3/rust-cpython you can directly `from .my_project import my_native_function`.
Expand Down
1 change: 1 addition & 0 deletions maturin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"rustc-extra-args",
"skip-auditwheel",
"strip",
"py-src"
]


Expand Down
15 changes: 13 additions & 2 deletions src/build_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,19 @@ pub enum ProjectLayout {

impl ProjectLayout {
/// Checks whether a python module exists besides Cargo.toml with the right name
pub fn determine(project_root: impl AsRef<Path>, module_name: &str) -> Result<ProjectLayout> {
let python_package_dir = project_root.as_ref().join(module_name);
pub fn determine(
project_root: impl AsRef<Path>,
module_name: &str,
py_src: Option<impl AsRef<Path>>,
) -> Result<ProjectLayout> {
let python_package_dir = if let Some(py_src) = py_src {
project_root
.as_ref()
.join(py_src.as_ref())
.join(module_name)
} else {
project_root.as_ref().join(module_name)
};
if python_package_dir.is_dir() {
if !python_package_dir.join("__init__.py").is_file() {
bail!("Found a directory with the module name ({}) next to Cargo.toml, which indicates a mixed python/rust project, but the directory didn't contain an __init__.py file.", module_name)
Expand Down
6 changes: 5 additions & 1 deletion src/build_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ pub struct BuildOptions {
/// directory in the project's target directory
#[structopt(short, long, parse(from_os_str))]
pub out: Option<PathBuf>,
/// Path to the Python source in mixed projects.
#[structopt(long = "py-src", parse(from_os_str))]
pub py_src: Option<PathBuf>,
/// [deprecated, use --manylinux instead] Don't check for manylinux compliance
#[structopt(long = "skip-auditwheel")]
pub skip_auditwheel: bool,
Expand All @@ -80,6 +83,7 @@ impl Default for BuildOptions {
interpreter: Some(vec![]),
bindings: None,
manifest_path: PathBuf::from("Cargo.toml"),
py_src: None,
out: None,
skip_auditwheel: false,
target: None,
Expand Down Expand Up @@ -120,7 +124,7 @@ impl BuildOptions {
.unwrap_or_else(|| &cargo_toml.package.name)
.to_owned();

let project_layout = ProjectLayout::determine(manifest_dir, &module_name)?;
let project_layout = ProjectLayout::determine(manifest_dir, &module_name, self.py_src)?;

let target = Target::from_target_triple(self.target.clone())?;

Expand Down
5 changes: 4 additions & 1 deletion src/develop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ use crate::PythonInterpreter;
use crate::Target;
use anyhow::{anyhow, format_err, Context, Result};
use std::fs;
use std::path::Path;
use std::path::{Path, PathBuf};

/// Installs a crate by compiling it and copying the shared library to the right directory
///
/// Works only in a virtualenv.
#[allow(clippy::too_many_arguments)]
pub fn develop(
bindings: Option<String>,
manifest_file: &Path,
Expand All @@ -20,6 +21,7 @@ pub fn develop(
venv_dir: &Path,
release: bool,
strip: bool,
py_src: Option<PathBuf>,
) -> Result<()> {
let target = Target::from_target_triple(None)?;

Expand All @@ -32,6 +34,7 @@ pub fn develop(
manifest_path: manifest_file.to_path_buf(),
out: None,
skip_auditwheel: false,
py_src,
target: None,
cargo_extra_args,
rustc_extra_args,
Expand Down
5 changes: 5 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ enum Opt {
/// Use as `--rustc-extra-args="--my-arg"`
#[structopt(long = "rustc-extra-args")]
rustc_extra_args: Vec<String>,
/// Path to the Python source in mixed projects.
#[structopt(long = "py-src", parse(from_os_str))]
py_src: Option<PathBuf>,
},
/// Build only a source distribution (sdist) without compiling.
///
Expand Down Expand Up @@ -438,6 +441,7 @@ fn run() -> Result<()> {
rustc_extra_args,
release,
strip,
py_src,
} => {
let venv_dir = match (env::var_os("VIRTUAL_ENV"),env::var_os("CONDA_PREFIX")) {
(Some(dir), None) => PathBuf::from(dir),
Expand All @@ -456,6 +460,7 @@ fn run() -> Result<()> {
&venv_dir,
release,
strip,
py_src,
)?;
}
Opt::SDist { manifest_path, out } => {
Expand Down
3 changes: 3 additions & 0 deletions test-crates/pyo3-mixed/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[build-system]
requires = ["maturin"]
build-backend = "maturin"

[tool.maturin]
py-src = "py-src"
2 changes: 1 addition & 1 deletion test-crates/pyo3-mixed/tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ skipsdist = true
whitelist_externals = cargo
deps = pytest
commands =
cargo run --manifest-path ../Cargo.toml -- develop # You'll want to use `maturin develop` here
cargo run --manifest-path ../../Cargo.toml -- develop --py-src=python # You'll want to use `maturin develop` here
pytest
2 changes: 1 addition & 1 deletion test-dockerfile.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ if [[ $(python test-crates/pyo3-pure/check_installed/check_installed.py) != 'SUC
exit 1
fi

docker run --rm -v $(pwd)/test-crates/pyo3-mixed:/io maturin build --no-sdist -i python3.6
docker run --rm -v $(pwd)/test-crates/pyo3-mixed:/io maturin build --no-sdist -i python3.6 --py-src python

pip install pyo3-mixed --no-index --find-links test-crates/pyo3-mixed/target/wheels/

Expand Down
21 changes: 15 additions & 6 deletions tests/test_develop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,40 @@ mod common;

#[test]
fn test_develop_pyo3_pure() {
handle_result(test_develop("test-crates/pyo3-pure", None));
handle_result(test_develop("test-crates/pyo3-pure", None, None));
}

#[test]
fn test_develop_pyo3_mixed() {
handle_result(test_develop("test-crates/pyo3-mixed", None));
handle_result(test_develop(
"test-crates/pyo3-mixed",
None,
Some("python".into()),
));
}

#[test]
fn test_develop_cffi_pure() {
handle_result(test_develop("test-crates/cffi-pure", None));
handle_result(test_develop("test-crates/cffi-pure", None, None));
}

#[test]
fn test_develop_cffi_mixed() {
handle_result(test_develop("test-crates/cffi-mixed", None));
handle_result(test_develop("test-crates/cffi-mixed", None, None));
}

#[test]
fn test_develop_hello_world() {
handle_result(test_develop("test-crates/hello-world", None));
handle_result(test_develop("test-crates/hello-world", None, None));
}

/// Creates a virtualenv and activates it, checks that the package isn't installed, uses
/// "maturin develop" to install it and checks it is working
fn test_develop(package: impl AsRef<Path>, bindings: Option<String>) -> Result<()> {
fn test_develop(
package: impl AsRef<Path>,
bindings: Option<String>,
py_src: Option<PathBuf>,
) -> Result<()> {
maybe_mock_cargo();

let test_name = package
Expand Down Expand Up @@ -96,6 +104,7 @@ fn test_develop(package: impl AsRef<Path>, bindings: Option<String>) -> Result<(
&venv_dir,
false,
cfg!(feature = "faster-tests"),
py_src.map(|src| src.into()),
)?;

check_installed(&package.as_ref(), &python)?;
Expand Down
38 changes: 30 additions & 8 deletions tests/test_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,39 +13,47 @@ mod common;

#[test]
fn test_integration_pyo3_pure() {
handle_result(test_integration("test-crates/pyo3-pure", None));
handle_result(test_integration("test-crates/pyo3-pure", None, None));
}

#[test]
fn test_integration_pyo3_mixed() {
handle_result(test_integration("test-crates/pyo3-mixed", None));
handle_result(test_integration(
"test-crates/pyo3-mixed",
None,
Some("python".into()),
));
}

#[cfg(target_os = "windows")]
#[test]
#[ignore]
fn test_integration_pyo3_pure_conda() {
handle_result(test_integration_conda("text-crates/pyo3-pure", None));
handle_result(test_integration_conda("text-crates/pyo3-pure", None, None));
}

#[test]
fn test_integration_cffi_pure() {
handle_result(test_integration("test-crates/cffi-pure", None));
handle_result(test_integration("test-crates/cffi-pure", None, None));
}

#[test]
fn test_integration_cffi_mixed() {
handle_result(test_integration("test-crates/cffi-mixed", None));
handle_result(test_integration("test-crates/cffi-mixed", None, None));
}

#[test]
fn test_integration_hello_world() {
handle_result(test_integration("test-crates/hello-world", None));
handle_result(test_integration("test-crates/hello-world", None, None));
}

/// For each installed python version, this builds a wheel, creates a virtualenv if it
/// doesn't exist, installs the package and runs check_installed.py
fn test_integration(package: impl AsRef<Path>, bindings: Option<String>) -> Result<()> {
fn test_integration(
package: impl AsRef<Path>,
bindings: Option<String>,
py_src: Option<String>,
) -> Result<()> {
maybe_mock_cargo();

let target = Target::from_target_triple(None)?;
Expand All @@ -65,6 +73,11 @@ fn test_integration(package: impl AsRef<Path>, bindings: Option<String>) -> Resu
cli.push(bindings);
}

if let Some(py_src) = py_src.as_ref() {
cli.push("--py-src");
cli.push(py_src);
}

let options = BuildOptions::from_iter_safe(cli)?;

let wheels = options
Expand Down Expand Up @@ -153,7 +166,11 @@ fn create_conda_env(name: &str, major: usize, minor: usize) {
}

#[cfg(target_os = "windows")]
fn test_integration_conda(package: impl AsRef<Path>, bindings: Option<String>) -> Result<()> {
fn test_integration_conda(
package: impl AsRef<Path>,
bindings: Option<String>,
py_src: Option<String>,
) -> Result<()> {
use std::env;

let package_string = package.as_ref().join("Cargo.toml").display().to_string();
Expand Down Expand Up @@ -188,6 +205,11 @@ fn test_integration_conda(package: impl AsRef<Path>, bindings: Option<String>) -
cli.push(bindings);
}

if let Some(py_src) = py_src.as_ref() {
cli.push("--py-src");
cli.push(py_src);
}

let options = BuildOptions::from_iter_safe(cli)?;

let wheels = options
Expand Down

0 comments on commit 2cb5c3e

Please sign in to comment.