Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce git source distribution generator #1587

Merged
merged 2 commits into from
May 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .cirrus.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ freebsd_task:
- echo $CIRRUS_OS
- cat Cargo.lock
install_script:
- pkg install -y bash python
- pkg install -y bash git python
- python3 -m ensurepip
<<: *BUILD_AND_TEST

Expand Down
8 changes: 6 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -287,12 +287,14 @@ jobs:
CARGO_TERM_COLOR: always
container: alpine:latest
steps:
- uses: actions/checkout@v3
- name: Install build requirements
run: |
set -ex
apk add cargo python3-dev libffi-dev py3-pip curl bash tar zstd
apk add cargo python3-dev libffi-dev py3-pip curl bash tar zstd git
pip3 install cffi virtualenv
- uses: actions/checkout@v3
- name: Fix git permissions
run: git config --global --add safe.directory $GITHUB_WORKSPACE
- name: Cache cargo build
uses: Swatinem/rust-cache@v2
with:
Expand Down Expand Up @@ -485,6 +487,8 @@ jobs:
container: pyston/pyston:2.3.5
steps:
- uses: actions/checkout@v3
- name: Fix git permissions
run: git config --global --add safe.directory $GITHUB_WORKSPACE
- uses: dtolnay/rust-toolchain@stable
id: rustup
with:
Expand Down
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Add support for configuring macOS deployment target version in `pyproject.toml` in [#1536](https://github.com/PyO3/maturin/pull/1536)
* Rewrite platform specific dependencies in `Cargo.toml` by viccie30 in [#1572](https://github.com/PyO3/maturin/pull/1572)
* Add trusted publisher support in [#1578](https://github.com/PyO3/maturin/pull/1578)
* Add new `git` source distribution generator in [#1587](https://github.com/PyO3/maturin/pull/1587)

## [0.14.17] - 2023-04-06

Expand Down
3 changes: 3 additions & 0 deletions guide/src/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ python-source = "src"
python-packages = ["foo", "bar"]
# Strip the library for minimum file size
strip = true
# Source distribution generator,
# supports cargo (default) and git.
sdist-generator = "cargo"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bikeshedding, please suggest if you have a better name for this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i like it!

```

The `[tool.maturin.include]` and `[tool.maturin.exclude]` configuration are
Expand Down
56 changes: 42 additions & 14 deletions src/pyproject_toml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ use std::collections::HashMap;
use std::path::{Path, PathBuf};

/// The `[tool]` section of a pyproject.toml
#[derive(Serialize, Deserialize, Debug, Clone)]
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "kebab-case")]
pub struct Tool {
maturin: Option<ToolMaturin>,
/// maturin options
pub maturin: Option<ToolMaturin>,
}

#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
Expand Down Expand Up @@ -104,28 +105,48 @@ pub struct TargetConfig {
pub macos_deployment_target: Option<String>,
}

/// Source distribution generator
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default)]
#[serde(rename_all = "kebab-case")]
pub enum SdistGenerator {
/// Use `cargo package --list`
#[default]
Cargo,
/// Use `git ls-files`
Git,
}

/// The `[tool.maturin]` section of a pyproject.toml
#[derive(Serialize, Deserialize, Debug, Clone)]
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "kebab-case")]
pub struct ToolMaturin {
// maturin specific options
// extension module name, accepts setuptools style import name like `foo.bar`
module_name: Option<String>,
include: Option<Vec<GlobPattern>>,
exclude: Option<Vec<GlobPattern>>,
bindings: Option<String>,
/// Module name, accepts setuptools style import name like `foo.bar`
pub module_name: Option<String>,
/// Include files matching the given glob pattern(s)
pub include: Option<Vec<GlobPattern>>,
/// Exclude files matching the given glob pattern(s)
pub exclude: Option<Vec<GlobPattern>>,
/// Bindings type
pub bindings: Option<String>,
/// Platform compatibility
#[serde(alias = "manylinux")]
compatibility: Option<PlatformTag>,
pub compatibility: Option<PlatformTag>,
/// Skip audit wheel
#[serde(default)]
pub skip_auditwheel: bool,
/// Strip the final binary
#[serde(default)]
skip_auditwheel: bool,
pub strip: bool,
/// Source distribution generator
#[serde(default)]
strip: bool,
pub sdist_generator: SdistGenerator,
/// The directory with python module, contains `<module_name>/__init__.py`
python_source: Option<PathBuf>,
pub python_source: Option<PathBuf>,
/// Python packages to include
python_packages: Option<Vec<String>>,
pub python_packages: Option<Vec<String>>,
/// Path to the wheel directory, defaults to `<module_name>.data`
data: Option<PathBuf>,
pub data: Option<PathBuf>,
/// Cargo compile targets
pub targets: Option<Vec<CargoTarget>>,
/// Target configuration
Expand Down Expand Up @@ -239,6 +260,13 @@ impl PyProjectToml {
.unwrap_or_default()
}

/// Returns the value of `[tool.maturin.sdist-generator]` in pyproject.toml
pub fn sdist_generator(&self) -> SdistGenerator {
self.maturin()
.map(|maturin| maturin.sdist_generator)
.unwrap_or_default()
}

/// Returns the value of `[tool.maturin.python-source]` in pyproject.toml
pub fn python_source(&self) -> Option<&Path> {
self.maturin()
Expand Down
137 changes: 105 additions & 32 deletions src/source_distribution.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::module_writer::{add_data, ModuleWriter};
use crate::pyproject_toml::SdistGenerator;
use crate::{pyproject_toml::Format, BuildContext, PyProjectToml, SDistWriter};
use anyhow::{bail, Context, Result};
use cargo_metadata::{Metadata, MetadataCommand};
Expand Down Expand Up @@ -564,29 +565,53 @@ fn find_path_deps(cargo_metadata: &Metadata) -> Result<HashMap<String, PathBuf>>
Ok(path_deps)
}

/// Creates a source distribution, packing the root crate and all local dependencies
/// Copies the files of git to a source distribution
///
/// The source distribution format is specified in
/// [PEP 517 under "build_sdist"](https://www.python.org/dev/peps/pep-0517/#build-sdist)
/// and in
/// https://packaging.python.org/specifications/source-distribution-format/#source-distribution-file-format
pub fn source_distribution(
/// Runs `git ls-files -z` to obtain a list of files to package.
fn add_git_tracked_files_to_sdist(
pyproject_toml_path: &Path,
writer: &mut SDistWriter,
prefix: impl AsRef<Path>,
) -> Result<()> {
let pyproject_dir = pyproject_toml_path.parent().unwrap();
let output = Command::new("git")
.args(["ls-files", "-z"])
.current_dir(pyproject_dir)
.output()
.context("Failed to run `git ls-files -z`")?;
if !output.status.success() {
bail!(
"Failed to query file list from git: {}\n--- Project Path: {}\n--- Stdout:\n{}\n--- Stderr:\n{}",
output.status,
pyproject_dir.display(),
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}

let prefix = prefix.as_ref();
writer.add_directory(prefix)?;

let file_paths = str::from_utf8(&output.stdout)
.context("git printed invalid utf-8 ಠ_ಠ")?
.split('\0')
.filter(|s| !s.is_empty())
.map(Path::new);
for source in file_paths {
writer.add_file(prefix.join(source), pyproject_dir.join(source))?;
}
Ok(())
}

/// Copies the files of a crate to a source distribution, recursively adding path dependencies
/// and rewriting path entries in Cargo.toml
fn add_cargo_package_files_to_sdist(
build_context: &BuildContext,
pyproject: &PyProjectToml,
excludes: Option<Override>,
) -> Result<PathBuf> {
let metadata21 = &build_context.metadata21;
pyproject_toml_path: &Path,
writer: &mut SDistWriter,
root_dir: &Path,
) -> Result<()> {
let manifest_path = &build_context.manifest_path;
let pyproject_toml_path = build_context
.pyproject_toml_path
.normalize()
.with_context(|| {
format!(
"failed to normalize path `{}`",
build_context.pyproject_toml_path.display()
)
})?
.into_path_buf();
let workspace_manifest_path = build_context
.cargo_metadata
.workspace_root
Expand All @@ -596,13 +621,6 @@ pub fn source_distribution(

let known_path_deps = find_path_deps(&build_context.cargo_metadata)?;

let mut writer = SDistWriter::new(&build_context.out, metadata21, excludes)?;
let root_dir = PathBuf::from(format!(
"{}-{}",
&metadata21.get_distribution_escaped(),
&metadata21.get_version_escaped()
));

// Add local path dependencies
let mut path_dep_workspace_manifests = HashMap::new();
for (name, path_dep) in known_path_deps.iter() {
Expand Down Expand Up @@ -635,8 +653,8 @@ pub fn source_distribution(
&path_dep_workspace_manifests[&path_dep_metadata.workspace_root]
};
add_crate_to_source_distribution(
&mut writer,
&pyproject_toml_path,
writer,
pyproject_toml_path,
path_dep,
path_dep_workspace_manifest,
&root_dir.join(LOCAL_DEPENDENCIES_FOLDER).join(name),
Expand All @@ -652,11 +670,11 @@ pub fn source_distribution(

// Add the main crate
add_crate_to_source_distribution(
&mut writer,
&pyproject_toml_path,
writer,
pyproject_toml_path,
manifest_path,
&workspace_manifest,
&root_dir,
root_dir,
&known_path_deps,
true,
)?;
Expand Down Expand Up @@ -733,6 +751,61 @@ pub fn source_distribution(
}
}

Ok(())
}

/// Creates a source distribution, packing the root crate and all local dependencies
///
/// The source distribution format is specified in
/// [PEP 517 under "build_sdist"](https://www.python.org/dev/peps/pep-0517/#build-sdist)
/// and in
/// https://packaging.python.org/specifications/source-distribution-format/#source-distribution-file-format
pub fn source_distribution(
build_context: &BuildContext,
pyproject: &PyProjectToml,
excludes: Option<Override>,
) -> Result<PathBuf> {
let pyproject_toml_path = build_context
.pyproject_toml_path
.normalize()
.with_context(|| {
format!(
"failed to normalize path `{}`",
build_context.pyproject_toml_path.display()
)
})?
.into_path_buf();
let metadata21 = &build_context.metadata21;
let mut writer = SDistWriter::new(&build_context.out, metadata21, excludes)?;
let root_dir = PathBuf::from(format!(
"{}-{}",
&metadata21.get_distribution_escaped(),
&metadata21.get_version_escaped()
));

match pyproject.sdist_generator() {
SdistGenerator::Cargo => add_cargo_package_files_to_sdist(
build_context,
&pyproject_toml_path,
&mut writer,
&root_dir,
)?,
SdistGenerator::Git => {
add_git_tracked_files_to_sdist(&pyproject_toml_path, &mut writer, &root_dir)?
}
}

let pyproject_toml_path = build_context
.pyproject_toml_path
.normalize()
.with_context(|| {
format!(
"failed to normalize path `{}`",
build_context.pyproject_toml_path.display()
)
})?
.into_path_buf();
let pyproject_dir = pyproject_toml_path.parent().unwrap();
// Add readme, license
if let Some(project) = pyproject.project.as_ref() {
if let Some(pyproject_toml::ReadMe::RelativePath(readme)) = project.readme.as_ref() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[build-system]
requires = ["maturin>=0.14,<0.15"]
build-backend = "maturin"

[tool.maturin]
manifest-path = "python/Cargo.toml"
19 changes: 18 additions & 1 deletion tests/common/other.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use anyhow::{Context, Result};
use clap::Parser;
use flate2::read::GzDecoder;
use maturin::pyproject_toml::{SdistGenerator, ToolMaturin};
use maturin::{BuildOptions, CargoOptions, PlatformTag};
use pretty_assertions::assert_eq;
use std::collections::BTreeSet;
Expand Down Expand Up @@ -115,6 +116,7 @@ pub fn test_workspace_cargo_lock() -> Result<()> {

pub fn test_source_distribution(
package: impl AsRef<Path>,
sdist_generator: SdistGenerator,
expected_files: Vec<&str>,
expected_cargo_toml: Option<(&Path, &str)>,
unique_name: &str,
Expand All @@ -135,7 +137,22 @@ pub fn test_source_distribution(
..Default::default()
};

let build_context = build_options.into_build_context(false, false, false)?;
let mut build_context = build_options.into_build_context(false, false, false)?;

// Override the sdist generator for testing
let mut pyproject_toml = build_context.pyproject_toml.take().unwrap();
let mut tool = pyproject_toml.tool.clone().unwrap_or_default();
if let Some(ref mut tool_maturin) = tool.maturin {
tool_maturin.sdist_generator = sdist_generator;
} else {
tool.maturin = Some(ToolMaturin {
sdist_generator,
..Default::default()
});
}
pyproject_toml.tool = Some(tool);
build_context.pyproject_toml = Some(pyproject_toml);

let (path, _) = build_context
.build_source_distribution()?
.context("Failed to build source distribution")?;
Expand Down
Loading