diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c75fa0536..b00ab9ee0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -82,7 +82,7 @@ jobs: - name: Build wheel (with sdist) if: matrix.target == 'x86_64-unknown-linux-musl' run: | - cargo run -- build --release -b bin --sdist -o dist --target ${{ matrix.target }} --features password-storage --compatibility manylinux2010 musllinux_1_1 + cargo run -- build --release -b bin --sdist -o dist --target ${{ matrix.target }} --compatibility manylinux2010 musllinux_1_1 # ring doesn't support aarch64 windows yet - name: Build wheel (windows aarch64) @@ -91,7 +91,7 @@ jobs: - name: Build wheel (without sdist) if: ${{ matrix.target != 'x86_64-unknown-linux-musl' && matrix.target != 'aarch64-pc-windows-msvc' }} - run: cargo run -- build --release -b bin -o dist --target ${{ matrix.target }} --features password-storage,static + run: cargo run -- build --release -b bin -o dist --target ${{ matrix.target }} --features static - name: Build wheel (macOS universal2) if: matrix.target == 'x86_64-apple-darwin' @@ -103,7 +103,7 @@ jobs: # set SDKROOT for C dependencies like ring and bzip2 export SDKROOT=$(xcrun --sdk macosx --show-sdk-path) rustup target add aarch64-apple-darwin - cargo run -- build --release -b bin -o dist --target universal2-apple-darwin --features password-storage,static + cargo run -- build --release -b bin -o dist --target universal2-apple-darwin --features static - name: Archive binary (windows) if: matrix.os == 'windows-latest' @@ -195,8 +195,7 @@ jobs: sudo python3 -m pip install -U --pre maturin maturin build --release -b bin -o dist \ --target ${{ matrix.platform.target }} \ - --compatibility ${{ matrix.platform.compatibility }} \ - --features password-storage + --compatibility ${{ matrix.platform.compatibility }} - name: Archive binary run: tar czvf target/release/maturin-${{ matrix.platform.target }}.tar.gz -C target/${{ matrix.platform.target }}/release maturin - name: Upload wheel artifacts diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index baf7e7042..47e54dd59 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -142,7 +142,7 @@ jobs: if: startsWith(matrix.os, 'windows') run: echo "RUSTFLAGS="-C debuginfo=0"" >> $GITHUB_ENV - name: cargo test - run: cargo nextest run --features password-storage + run: cargo nextest run # TODO: https://github.com/PyO3/maturin/issues/2263 #- name: test cross compiling with zig # if: ${{ !contains(matrix.python-version, '-dev') }} @@ -307,7 +307,7 @@ jobs: - name: cargo test run: | # unset GITHUB_ACTIONS env var to disable zig related tests - env -u GITHUB_ACTIONS cargo nextest run --features password-storage + env -u GITHUB_ACTIONS cargo nextest run test-auditwheel: name: Test Auditwheel diff --git a/Cargo.lock b/Cargo.lock index 397aa2d2c..afff8a60b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -172,12 +172,6 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" -[[package]] -name = "bytesize" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c99fa31e08a43eaa5913ef68d7e01c37a2bdce6ed648168239ad33b7d30a9cd8" - [[package]] name = "bzip2" version = "0.6.1" @@ -501,12 +495,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" -[[package]] -name = "configparser" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e57e3272f0190c3f1584272d613719ba5fc7df7f4942fe542e63d949cf3a649b" - [[package]] name = "console" version = "0.15.11" @@ -670,7 +658,6 @@ dependencies = [ "console 0.15.11", "shell-words", "thiserror 1.0.69", - "zeroize", ] [[package]] @@ -1254,16 +1241,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "keyring" -version = "2.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "363387f0019d714aa60cc30ab4fe501a747f4c08fc58f069dd14be971bd495a0" -dependencies = [ - "lazy_static", - "linux-keyutils", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -1323,16 +1300,6 @@ dependencies = [ "zlib-rs", ] -[[package]] -name = "linux-keyutils" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "761e49ec5fd8a5a463f9b84e877c373d888935b71c6be78f3767fe2ae6bed18e" -dependencies = [ - "bitflags", - "libc", -] - [[package]] name = "linux-raw-sys" version = "0.11.0" @@ -1413,7 +1380,6 @@ version = "1.10.2" dependencies = [ "anyhow", "base64", - "bytesize", "cargo-config2", "cargo-options", "cargo-xwin", @@ -1423,10 +1389,8 @@ dependencies = [ "cc", "clap", "clap_complete_command", - "configparser", "console 0.16.1", "dialoguer", - "dirs", "dunce", "expect-test", "fat-macho", @@ -1440,11 +1404,8 @@ dependencies = [ "indoc", "insta", "itertools 0.14.0", - "keyring", "lddtree", "minijinja", - "multipart", - "native-tls", "normpath", "once_cell", "path-slash", @@ -1458,8 +1419,6 @@ dependencies = [ "rstest", "rustc_version", "rustflags", - "rustls", - "rustls-pemfile", "rustversion", "same-file", "schemars", @@ -1480,10 +1439,8 @@ dependencies = [ "tracing-subscriber", "trycmd", "unicode-xid", - "ureq", "url", "which", - "wild", "xz2", "zip 6.0.0", ] @@ -1503,22 +1460,6 @@ dependencies = [ "libmimalloc-sys", ] -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "mime_guess" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" -dependencies = [ - "mime", - "unicase", -] - [[package]] name = "minijinja" version = "2.12.0" @@ -1556,19 +1497,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "multipart" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" -dependencies = [ - "log", - "mime", - "mime_guess", - "rand", - "tempfile", -] - [[package]] name = "native-tls" version = "0.2.14" @@ -2980,12 +2908,6 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" -[[package]] -name = "unicase" -version = "2.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" - [[package]] name = "unicode-ident" version = "1.0.22" @@ -3244,15 +3166,6 @@ dependencies = [ "winsafe", ] -[[package]] -name = "wild" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3131afc8c575281e1e80f36ed6a092aa502c08b18ed7524e86fbbb12bb410e1" -dependencies = [ - "glob", -] - [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 9a1e3ee6b..b4cead744 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ authors = ["konstin ", "messense "] name = "maturin" version = "1.10.2" -description = "Build and publish crates with pyo3, cffi and uniffi bindings as well as rust binaries as python packages" +description = "Build crates with pyo3, cffi and uniffi bindings as well as rust binaries as python packages" exclude = [ "test-crates/**/*", "sysconfig/*", @@ -113,30 +113,6 @@ dialoguer = { version = "0.11.0", default-features = false, optional = true } console = { version = "0.16.0", optional = true } minijinja = { version = "2.5.0", optional = true } -# upload -bytesize = { version = "2.2.0", optional = true } -configparser = { version = "3.0.3", optional = true } -dirs = { version = "5.0.0", optional = true } -multipart = { version = "0.18.0", features = [ - "client", -], default-features = false, optional = true } -ureq = { version = "2.9.4", features = [ - "gzip", - "json", - "socks-proxy", -], default-features = false, optional = true } -native-tls = { version = "0.2.8", optional = true } -rustls = { version = "0.23", default-features = false, features = [ - "logging", - "std", - "tls12", -], optional = true } -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.1.0", optional = true } - # JSON schema schemars = { version = "1.0.4", optional = true } pretty_assertions = { version = "1.3.0", optional = true } @@ -157,36 +133,17 @@ which = "7.0.0" [features] default = ["full", "rustls"] -full = ["cli-completion", "cross-compile", "scaffolding", "upload"] +full = ["cli-completion", "cross-compile", "scaffolding"] cli-completion = ["dep:clap_complete_command"] -upload = [ - "ureq", - "multipart", - "configparser", - "bytesize", - "dialoguer/password", - "wild", - "dep:dirs", -] - schemars = ["dep:schemars", "dep:pretty_assertions"] -# keyring doesn't support *BSD so it's not enabled in `full` by default -password-storage = ["upload", "keyring"] - rustls = [ - "dep:rustls", - "ureq?/tls", "cargo-xwin?/rustls-tls", - "dep:rustls-pemfile", ] native-tls = [ - "dep:native-tls", - "ureq?/native-tls", "cargo-xwin?/native-tls", - "dep:rustls-pemfile", ] # cross compile using zig or xwin @@ -203,10 +160,6 @@ static = ["xz2/static"] # Internal feature to speed up the tests significantly faster-tests = [] -# Deprecated features, keep them now for compatibility -human-panic = [] -log = [] - [profile.profiling] inherits = "release" debug = true diff --git a/Dockerfile b/Dockerfile index e060cb509..11852b72f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,7 +22,7 @@ RUN --mount=type=cache,target=/root/.cargo/git \ mkdir /maturin/src && \ touch /maturin/src/lib.rs && \ echo 'fn main() { println!("Dummy") }' > /maturin/src/main.rs && \ - cargo rustc --target $CARGO_BUILD_TARGET --bin maturin --manifest-path /maturin/Cargo.toml --release --features password-storage -- -C link-arg=-s + cargo rustc --target $CARGO_BUILD_TARGET --bin maturin --manifest-path /maturin/Cargo.toml --release -- -C link-arg=-s ADD . /maturin/ @@ -32,7 +32,7 @@ RUN touch /maturin/src/lib.rs /maturin/src/main.rs RUN --mount=type=cache,target=/root/.cargo/git \ --mount=type=cache,target=/root/.cargo/registry \ --mount=type=cache,target=/maturin/target,sharing=locked \ - cargo rustc --target $CARGO_BUILD_TARGET --bin maturin --manifest-path /maturin/Cargo.toml --release --features password-storage -- -C link-arg=-s \ + cargo rustc --target $CARGO_BUILD_TARGET --bin maturin --manifest-path /maturin/Cargo.toml --release -- -C link-arg=-s \ && mv /maturin/target/$CARGO_BUILD_TARGET/release/maturin /usr/bin/maturin FROM base-$TARGETARCH diff --git a/README.md b/README.md index 76dfe60ec..d5fe21be2 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ _formerly pyo3-pack_ [![PyPI](https://img.shields.io/pypi/v/maturin.svg?logo=python&style=flat-square)](https://pypi.org/project/maturin) [![discord server](https://img.shields.io/discord/1209263839632424990?logo=discord&style=flat-square)](https://discord.gg/33kcChzH7f) -Build and publish crates with [pyo3, cffi and uniffi bindings](https://maturin.rs/bindings) as well as rust binaries as python packages with minimal configuration. -It supports building wheels for python 3.8+ on Windows, Linux, macOS and FreeBSD, can upload them to [pypi](https://pypi.org/) and has basic PyPy and GraalPy support. +Build crates with [pyo3, cffi and uniffi bindings](https://maturin.rs/bindings) as well as rust binaries as python packages with minimal configuration. +It supports building wheels for python 3.8+ on Windows, Linux, macOS and FreeBSD and has basic PyPy and GraalPy support. Check out the [User Guide](https://maturin.rs/)! @@ -30,8 +30,7 @@ uv tool install maturin There are four main commands: - `maturin new` creates a new cargo project with maturin configured. -- `maturin publish` builds the crate into python packages and publishes them to pypi. -- `maturin build` builds the wheels and stores them in a folder (`target/wheels` by default), but doesn't upload them. It's recommended to publish packages with [uv](https://github.com/astral-sh/uv) using `uv publish`. +- `maturin build` builds the wheels and stores them in a folder (`target/wheels` by default). It's recommended to publish packages with [uv](https://github.com/astral-sh/uv) using `uv publish`. - `maturin develop` builds the crate and installs it as a python module directly in the current virtualenv. Note that while `maturin develop` is faster, it doesn't support all the feature that running `pip install` after `maturin build` supports. maturin doesn't need extra configuration files and doesn't clash with an existing setuptools-rust configuration. diff --git a/deny.toml b/deny.toml index 734700059..6ac8cda29 100644 --- a/deny.toml +++ b/deny.toml @@ -75,8 +75,6 @@ feature-depth = 1 # A list of advisory IDs to ignore. Note that ignored advisories will still # output a note when they are encountered. ignore = [ - # multipart unmaintained - "RUSTSEC-2023-0050", # paste unmaintained "RUSTSEC-2024-0436", # num_prefix unmaintained diff --git a/guide/src/contributing.md b/guide/src/contributing.md index 1d9970e08..65212e00f 100644 --- a/guide/src/contributing.md +++ b/guide/src/contributing.md @@ -87,7 +87,7 @@ run `mdbook watch guide` from the repository root. The output can then be found ## Code -The main part is the maturin library, which is completely documented and should be well integrable. The accompanying `main.rs` takes care username and password for the pypi upload and otherwise calls into the library. +The main part is the maturin library, which is completely documented and should be well integrable. The accompanying `main.rs` calls into the library. The `sysconfig` folder contains the output of `python -m sysconfig` for different python versions and platform, which is helpful during development. diff --git a/guide/src/environment-variables.md b/guide/src/environment-variables.md index 1d4eb98b7..b3354b280 100644 --- a/guide/src/environment-variables.md +++ b/guide/src/environment-variables.md @@ -9,12 +9,10 @@ See [environment variables Cargo reads](https://doc.rust-lang.org/cargo/referenc ## Python environment variables -* `VIRTUAL_ENV`: Path to a Python virtual environment -* `CONDA_PREFIX`: Path to a conda environment -* `_PYTHON_SYSCONFIGDATA_NAME`: Name of a `sysconfigdata*.py` file -* `MATURIN_PYPI_TOKEN`: PyPI token for uploading wheels -* `MATURIN_PASSWORD`: PyPI password for uploading wheels -* `MATURIN_PEP517_USE_BASE_PYTHON`: Use base Python executable instead of venv Python executable in PEP 517 build to avoid unnecessary rebuilds, should not be set when the sdist build requires packages installed in venv. +- `VIRTUAL_ENV`: Path to a Python virtual environment +- `CONDA_PREFIX`: Path to a conda environment +- `_PYTHON_SYSCONFIGDATA_NAME`: Name of a `sysconfigdata*.py` file +- `MATURIN_PEP517_USE_BASE_PYTHON`: Use base Python executable instead of venv Python executable in PEP 517 build to avoid unnecessary rebuilds, should not be set when the sdist build requires packages installed in venv. ## `pyo3` environment variables diff --git a/guide/src/tutorial.md b/guide/src/tutorial.md index 290b594ae..e13dfa85f 100644 --- a/guide/src/tutorial.md +++ b/guide/src/tutorial.md @@ -288,9 +288,6 @@ wheels compatible with a wide range of Linux distros. 📦 Built wheel for abi3 Python ≥ 3.8 to /Users/ferris/src/rust/guessing-game/target/wheels/guessing_game-0.1.0-cp37-abi3-macosx_10_7_x86_64.whl ``` -maturin can even publish wheels directly to [PyPI](https://pypi.org) with -`maturin publish`! - ## Summary Congratulations! You successfully created a Python module implemented entirely diff --git a/pyproject.toml b/pyproject.toml index c1fcb4b1d..a28a7bd15 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ build-backend = "bootstrap" [project] name = "maturin" -description = "Build and publish crates with pyo3, cffi and uniffi bindings as well as rust binaries as python packages" +description = "Build crates with pyo3, cffi and uniffi bindings as well as rust binaries as python packages" authors = [{ name = "konstin", email = "konstin@mailbox.org" }] readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.7" diff --git a/src/lib.rs b/src/lib.rs index c4d368b83..baa689317 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,21 +5,25 @@ //! //! # Cargo features //! -//! Default features: log, upload, rustls +//! Default features: full, rustls //! -//! - log: Configures pretty-env-logger, even though maturin doesn't use logging itself. +//! - full: Enables the cli-completion, cross-compile, and scaffolding features //! -//! - upload: Uses ureq to add the upload command. +//! - cli-completion: Enables the generation of shell completion for the maturin CLI //! -//! - rustls: Makes ureq use the rustls stack so that we can build maturin in a CentOS 6 -//! docker container and which maturin itself manylinux compliant. +//! - cross-compile: Enables cross compiling using either zig or xwin //! -//! - native-tls: Makes ureq use the platform native tls stack +//! - scaffolding: Enables the 'maturin new' and 'maturin generate-ci' commands //! -//! - password-storage (off by default): Uses the keyring package to store the password. keyring -//! pulls in a lot of shared libraries and outdated dependencies, so this is off by default, except -//! for the build on the github releases page. -//! (https://github.com/hwchen/secret-service-rs/issues/9) +//! - schemars: Enables the `maturin generate-json-schema` to generate a JSON schema +//! for the `tool.maturin` section of the pyproject.toml file +//! +//! - static: Builds maturin with statically linked dependencies +//! +//! - rustls: Makes dependencies use the rustls stack so that we can build maturin in a +//! CentOS 6 docker container and which makes maturin itself manylinux compliant. +//! +//! - native-tls: Makes dependencies use the platform native tls stack #![deny(missing_docs)] @@ -41,8 +45,6 @@ pub use crate::new_project::{GenerateProjectOptions, init_project, new_project}; pub use crate::pyproject_toml::PyProjectToml; pub use crate::python_interpreter::PythonInterpreter; pub use crate::source_distribution::find_path_deps; -#[cfg(feature = "upload")] -pub use crate::upload::{PublishOpt, Registry, UploadError, upload, upload_ui}; pub use auditwheel::PlatformTag; pub use target::Target; @@ -70,5 +72,3 @@ pub mod pyproject_toml; mod python_interpreter; mod source_distribution; mod target; -#[cfg(feature = "upload")] -mod upload; diff --git a/src/main.rs b/src/main.rs index e25646f11..b88c4b6d6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -//! Build and publish crates with pyo3, cffi and uniffi bindings as well as rust binaries +//! Build crates with pyo3, cffi and uniffi bindings as well as rust binaries //! as python packages. This file contains the CLI and keyring integration. //! //! Run with --help for usage information @@ -19,8 +19,6 @@ use maturin::{ use maturin::{GenerateJsonSchemaOptions, generate_json_schema}; #[cfg(feature = "scaffolding")] use maturin::{GenerateProjectOptions, ci::GenerateCI, init_project, new_project}; -#[cfg(feature = "upload")] -use maturin::{PublishOpt, upload_ui}; use std::env; use std::path::PathBuf; use std::str::FromStr; @@ -36,7 +34,7 @@ use tracing_subscriber::{EnvFilter, Layer, layer::SubscriberExt, util::Subscribe after_help = "Visit https://maturin.rs to learn more about maturin.", styles = cargo_options::styles(), )] -/// Build and publish crates with pyo3, cffi and uniffi bindings as well +/// Build crates with pyo3, cffi and uniffi bindings as well /// as rust binaries as python packages struct Opt { /// Use verbose output. @@ -56,7 +54,7 @@ struct Opt { #[derive(Debug, Parser)] #[allow(clippy::large_enum_variant)] -/// Build and publish crates with pyo3, cffi and uniffi bindings as well +/// Build crates with pyo3, cffi and uniffi bindings as well /// as rust binaries as python packages enum Command { #[command(name = "build", alias = "b")] @@ -74,24 +72,6 @@ enum Command { #[command(flatten)] build: BuildOptions, }, - #[cfg(feature = "upload")] - #[command(name = "publish")] - /// Build and publish the crate as python packages to pypi - Publish { - /// Do not pass --release to cargo - #[arg(long, conflicts_with = "profile")] - debug: bool, - /// Do not strip the library for minimum file size - #[arg(long = "no-strip")] - no_strip: bool, - /// Don't build a source distribution - #[arg(long = "no-sdist")] - no_sdist: bool, - #[command(flatten)] - publish: PublishOpt, - #[command(flatten)] - build: BuildOptions, - }, #[command(name = "list-python")] /// Search and list the available python installations ListPython { @@ -137,19 +117,6 @@ enum Command { #[cfg(feature = "scaffolding")] #[command(name = "generate-ci")] GenerateCI(GenerateCI), - /// Upload python packages to pypi - /// - /// It is mostly similar to `twine upload`, but can only upload python wheels - /// and source distributions. - #[cfg(feature = "upload")] - #[command(name = "upload")] - Upload { - #[command(flatten)] - publish: PublishOpt, - /// The python packages to upload - #[arg(value_name = "FILE")] - files: Vec, - }, /// Backend for the PEP 517 integration. Not for human consumption /// /// The commands are meant to be called from the python PEP 517 @@ -362,10 +329,7 @@ fn run() -> Result<()> { } } - #[cfg(not(feature = "wild"))] let opt = Opt::parse(); - #[cfg(feature = "wild")] - let opt = Opt::parse_from(wild::args_os()); setup_logging(opt.verbose)?; @@ -393,51 +357,6 @@ fn run() -> Result<()> { let wheels = build_context.build_wheels()?; assert!(!wheels.is_empty()); } - #[cfg(feature = "upload")] - Command::Publish { - mut build, - mut publish, - debug, - no_strip, - no_sdist, - } => { - // set profile to dev if specified; `--debug` and `--profile` are mutually exclusive - // - // do it here to take precedence over pyproject.toml profile setting - if debug { - build.profile = Some("dev".to_string()); - } - - let mut build_context = build - .into_build_context() - .strip(!no_strip) - .editable(false) - .build()?; - - // ensure profile always set when publishing - // (respect pyproject.toml if set) - // don't need to check `debug` here, set above to take precedence if set - let profile = build_context - .cargo_options - .profile - .get_or_insert_with(|| "release".to_string()); - - if profile == "dev" { - eprintln!("⚠️ Warning: You're publishing debug wheels"); - } - - let mut wheels = build_context.build_wheels()?; - if !no_sdist { - if let Some(sd) = build_context.build_source_distribution()? { - wheels.push(sd); - } - } - - let items = wheels.into_iter().map(|wheel| wheel.0).collect::>(); - publish.non_interactive_on_ci(); - - upload_ui(&items, &publish)? - } Command::ListPython { target } => { let found = if target.is_some() { let target = Target::from_target_triple(target.as_ref())?; @@ -502,16 +421,6 @@ fn run() -> Result<()> { Command::NewProject { path, options } => new_project(path, options)?, #[cfg(feature = "scaffolding")] Command::GenerateCI(generate_ci) => generate_ci.execute()?, - #[cfg(feature = "upload")] - Command::Upload { mut publish, files } => { - if files.is_empty() { - eprintln!("⚠️ Warning: No files given, exiting."); - return Ok(()); - } - publish.non_interactive_on_ci(); - - upload_ui(&files, &publish)? - } #[cfg(feature = "cli-completion")] Command::Completions { shell } => { shell.generate(&mut Opt::command(), &mut std::io::stdout()); diff --git a/src/metadata.rs b/src/metadata.rs index 345ebe333..6707b5e3c 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -542,8 +542,8 @@ impl Metadata24 { } /// Formats the metadata into a list where keys with multiple values - /// become multiple single-valued key-value pairs. This format is needed for the pypi - /// uploader and for the METADATA file inside wheels + /// become multiple single-valued key-value pairs. This format is needed for + /// the METADATA file inside wheels pub fn to_vec(&self) -> Vec<(String, String)> { let mut fields = vec![ ("Metadata-Version", self.metadata_version.clone()), diff --git a/src/upload.rs b/src/upload.rs deleted file mode 100644 index 1d646d6b7..000000000 --- a/src/upload.rs +++ /dev/null @@ -1,652 +0,0 @@ -//! The uploading logic was mostly reverse engineered; I wrote it down as -//! documentation at https://warehouse.readthedocs.io/api-reference/legacy/#upload-api - -use crate::build_context::hash_file; -use anyhow::{Context, Result, bail}; -use base64::Engine; -use base64::engine::general_purpose::STANDARD; -use bytesize::ByteSize; -use configparser::ini::Ini; -use fs_err as fs; -use fs_err::File; -use multipart::client::lazy::Multipart; -use regex::Regex; -use serde::Deserialize; -use std::collections::HashMap; -use std::env; -#[cfg(any(feature = "native-tls", feature = "rustls"))] -use std::ffi::OsString; -use std::io; -use std::path::{Path, PathBuf}; -use std::time::Duration; -use thiserror::Error; -use tracing::debug; - -/// An account with a registry, possibly incomplete -#[derive(Debug, clap::Parser)] -pub struct PublishOpt { - /// The repository (package index) to upload the package to. Should be a section in the config file. - /// - /// Can also be set via MATURIN_REPOSITORY environment variable. - #[arg(short = 'r', long, env = "MATURIN_REPOSITORY", default_value = "pypi")] - repository: String, - /// The URL of the registry where the wheels are uploaded to. This overrides --repository. - /// - /// Can also be set via MATURIN_REPOSITORY_URL environment variable. - #[arg(long, env = "MATURIN_REPOSITORY_URL", overrides_with = "repository")] - repository_url: Option, - /// Username for pypi or your custom registry. - /// - /// Can also be set via MATURIN_USERNAME environment variable. - /// - /// Set MATURIN_PYPI_TOKEN variable to use token-based authentication instead - #[arg(short, long, env = "MATURIN_USERNAME")] - username: Option, - /// Password for pypi or your custom registry. - /// - /// Can also be set via MATURIN_PASSWORD environment variable. - #[arg(short, long, env = "MATURIN_PASSWORD", hide_env_values = true)] - password: Option, - /// Continue uploading files if one already exists. - /// (Only valid when uploading to PyPI. Other implementations may not support this.) - #[arg(long)] - skip_existing: bool, - /// Do not interactively prompt for username/password if the required credentials are missing. - /// - /// Can also be set via MATURIN_NON_INTERACTIVE environment variable. - #[arg(long, env = "MATURIN_NON_INTERACTIVE")] - non_interactive: bool, -} - -impl PublishOpt { - // Here we omit trailing slashes from the repository URL, which we'll add back in `complete_registry` - const DEFAULT_REPOSITORY_URL: &'static str = "https://upload.pypi.org/legacy"; - const TEST_REPOSITORY_URL: &'static str = "https://test.pypi.org/legacy"; - - /// Set to non interactive mode if we're running on CI - pub fn non_interactive_on_ci(&mut self) { - let msg = "⚠️ Warning: The maturin upload and publish commands are deprecated and will be removed in the future. For more information see: https://github.com/PyO3/maturin/issues/2334"; - eprintln!("{msg}"); - if env::var("GITHUB_ACTIONS") - .map(|v| v == "true") - .unwrap_or_default() - { - // Also emit a warning annotation on the GH action - println!("::warning::{msg}"); - } - - if !self.non_interactive && env::var("CI").map(|v| v == "true").unwrap_or_default() { - eprintln!("🎛️ Running in non-interactive mode on CI"); - self.non_interactive = true; - } - } -} - -/// Error type for different types of errors that can happen when uploading a -/// wheel. -/// -/// The most interesting type is AuthenticationError because it allows asking -/// the user to reenter the password -#[derive(Error, Debug)] -#[error("Uploading to the registry failed")] -pub enum UploadError { - /// Any ureq error - #[error("Http error")] - UreqError(#[source] Box), - /// The registry returned a "403 Forbidden" - #[error("Username or password are incorrect")] - AuthenticationError(String), - /// Reading the wheel failed - #[error("IO Error")] - IoError(#[source] io::Error), - /// The registry returned something else than 200 - #[error("Failed to upload the wheel with status {0}: {1}")] - StatusCodeError(String, String), - /// File already exists - #[error("File already exists: {0}")] - FileExistsError(String), - /// Read package metadata error - #[error("Could not read the metadata from the package at {0}")] - PkgInfoError(PathBuf, #[source] python_pkginfo::Error), - /// TLS error - #[cfg(feature = "native-tls")] - #[error("TLS Error")] - TlsError(#[source] native_tls::Error), -} - -impl From for UploadError { - fn from(error: io::Error) -> Self { - UploadError::IoError(error) - } -} - -impl From for UploadError { - fn from(error: ureq::Error) -> Self { - UploadError::UreqError(Box::new(error)) - } -} - -#[cfg(feature = "native-tls")] -impl From for UploadError { - fn from(error: native_tls::Error) -> Self { - UploadError::TlsError(error) - } -} - -/// A pip registry such as pypi or testpypi with associated credentials, used -/// for uploading wheels -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct Registry { - /// The username - pub username: String, - /// The password - pub password: String, - /// The url endpoint for legacy uploading - pub url: String, -} - -impl Registry { - /// Creates a new registry - pub fn new(username: String, password: String, url: String) -> Registry { - Registry { - username, - password, - url, - } - } -} - -/// Attempts to fetch the password from the keyring (if enabled) -/// and falls back to the interactive password prompt. -fn get_password(_username: &str) -> String { - #[cfg(feature = "keyring")] - { - let service = env!("CARGO_PKG_NAME"); - let keyring = keyring::Entry::new(service, _username); - if let Ok(password) = keyring.and_then(|keyring| keyring.get_password()) { - return password; - }; - } - - dialoguer::Password::new() - .with_prompt("Please enter your password") - .interact() - .unwrap_or_else(|_| { - // So we need this fallback for pycharm on windows - let mut password = String::new(); - io::stdin() - .read_line(&mut password) - .expect("Failed to read line"); - password.trim().to_string() - }) -} - -fn get_username() -> String { - eprintln!("Please enter your username:"); - let mut line = String::new(); - io::stdin().read_line(&mut line).unwrap(); - line.trim().to_string() -} - -fn load_pypirc() -> Ini { - let mut config = Ini::new(); - if let Some(mut config_path) = dirs::home_dir() { - config_path.push(".pypirc"); - if let Ok(pypirc) = fs::read_to_string(config_path.as_path()) { - let _ = config.read(pypirc); - } - } - config -} - -fn load_pypi_cred_from_config(config: &Ini, registry_name: &str) -> Option<(String, String)> { - if let (Some(username), Some(password)) = ( - config.get(registry_name, "username"), - config.get(registry_name, "password"), - ) { - return Some((username, password)); - } - None -} - -/// Gets the PyPI credentials from (in precedence order): -/// -/// 1. `MATURIN_PYPI_TOKEN` environment variable -/// 2. `.pypirc` config file -/// 3. maturin command arguments -/// 4. `MATURIN_USERNAME` and `MATURIN_PASSWORD` environment variables -/// 5. the password keyring -/// 6. interactive prompt -fn resolve_pypi_cred( - opt: &PublishOpt, - config: &Ini, - registry_name: Option<&str>, - registry_url: &str, -) -> Result<(String, String)> { - // API token from environment variable takes priority - if let Ok(token) = env::var("MATURIN_PYPI_TOKEN") { - return Ok(("__token__".to_string(), token)); - } - - // Try to get a token via OIDC exchange - match resolve_pypi_token_via_oidc(registry_url) { - Ok(Some(token)) => { - eprintln!("🔐 Using trusted publisher for upload"); - return Ok(("__token__".to_string(), token)); - } - Ok(None) => {} - Err(e) => eprintln!("⚠️ Warning: Failed to resolve PyPI token via OIDC: {e}"), - } - - if let Some((username, password)) = - registry_name.and_then(|name| load_pypi_cred_from_config(config, name)) - { - eprintln!("🔐 Using credential in pypirc for upload"); - return Ok((username, password)); - } - - // fallback to username and password - if opt.non_interactive && (opt.username.is_none() || opt.password.is_none()) { - bail!("Credentials not found and non-interactive mode is enabled"); - } - let username = opt.username.clone().unwrap_or_else(get_username); - let password = opt - .password - .clone() - .unwrap_or_else(|| get_password(&username)); - Ok((username, password)) -} - -#[derive(Debug, Deserialize)] -struct OidcAudienceResponse { - audience: String, -} - -#[derive(Debug, Deserialize)] -struct OidcTokenResponse { - value: String, -} - -#[derive(Debug, Deserialize)] -struct MintTokenResponse { - token: String, -} - -/// Trusted Publisher support for GitHub Actions -fn resolve_pypi_token_via_oidc(registry_url: &str) -> Result> { - if env::var_os("GITHUB_ACTIONS").is_none() { - return Ok(None); - } - if let (Ok(req_token), Ok(req_url)) = ( - env::var("ACTIONS_ID_TOKEN_REQUEST_TOKEN"), - env::var("ACTIONS_ID_TOKEN_REQUEST_URL"), - ) { - let registry_url = url::Url::parse(registry_url)?; - let mut audience_url = registry_url.clone(); - audience_url.set_path("_/oidc/audience"); - debug!("Requesting OIDC audience from {}", audience_url); - let agent = http_agent()?; - let audience_res = agent - .get(audience_url.as_str()) - .timeout(Duration::from_secs(30)) - .call()?; - if audience_res.status() == 404 { - // OIDC is not enabled/supported on this registry - return Ok(None); - } - let audience = audience_res.into_json::()?.audience; - - debug!("Requesting OIDC token for {} from {}", audience, req_url); - let request_token_res: OidcTokenResponse = agent - .get(&req_url) - .query("audience", &audience) - .set("Authorization", &format!("bearer {req_token}")) - .timeout(Duration::from_secs(30)) - .call()? - .into_json()?; - let oidc_token = request_token_res.value; - - let mut mint_token_url = registry_url; - mint_token_url.set_path("_/oidc/github/mint-token"); - debug!("Requesting API token from {}", mint_token_url); - let mut mint_token_req = HashMap::new(); - mint_token_req.insert("token", oidc_token); - let mint_token_res = agent - .post(mint_token_url.as_str()) - .timeout(Duration::from_secs(30)) - .send_json(mint_token_req)? - .into_json::()?; - return Ok(Some(mint_token_res.token)); - } - Ok(None) -} - -/// Asks for username and password for a registry account where missing. -fn complete_registry(opt: &PublishOpt) -> Result { - // load creds from pypirc if found - let pypirc = load_pypirc(); - let (registry_name, registry_url) = if let Some(repository_url) = opt.repository_url.as_deref() - { - // to normalize URLs by removing trailing slashes - match repository_url.trim_end_matches('/') { - PublishOpt::DEFAULT_REPOSITORY_URL => ( - Some("pypi"), - // Add trailing slash back - format!("{}/", PublishOpt::DEFAULT_REPOSITORY_URL), - ), - PublishOpt::TEST_REPOSITORY_URL => ( - Some("testpypi"), - // Add trailing slash back - format!("{}/", PublishOpt::TEST_REPOSITORY_URL), - ), - _ => (None, repository_url.to_string()), - } - } else if let Some(url) = pypirc.get(&opt.repository, "repository") { - (Some(opt.repository.as_str()), url) - } else if opt.repository == "pypi" { - ( - Some("pypi"), - // Add trailing slash back - format!("{}/", PublishOpt::DEFAULT_REPOSITORY_URL), - ) - } else if opt.repository == "testpypi" { - ( - Some("testpypi"), - // Add trailing slash back - format!("{}/", PublishOpt::TEST_REPOSITORY_URL), - ) - } else { - bail!( - "Failed to get registry {} in .pypirc. \ - Note: Your index didn't start with http:// or https://, \ - which is required for non-pypirc indices.", - opt.repository - ); - }; - let (username, password) = resolve_pypi_cred(opt, &pypirc, registry_name, ®istry_url)?; - let registry = Registry::new(username, password, registry_url); - - Ok(registry) -} - -/// Port of pip's `canonicalize_name` -/// https://github.com/pypa/pip/blob/b33e791742570215f15663410c3ed987d2253d5b/src/pip/_vendor/packaging/utils.py#L18-L25 -fn canonicalize_name(name: &str) -> String { - Regex::new("[-_.]+") - .unwrap() - .replace_all(name, "-") - .to_lowercase() -} - -#[cfg(any(feature = "native-tls", feature = "rustls"))] -fn tls_ca_bundle() -> Option { - env::var_os("MATURIN_CA_BUNDLE") - .or_else(|| env::var_os("REQUESTS_CA_BUNDLE")) - .or_else(|| env::var_os("CURL_CA_BUNDLE")) -} - -// Prefer rustls if both native-tls and rustls features are enabled -#[cfg(all(feature = "native-tls", not(feature = "rustls")))] -#[allow(clippy::result_large_err)] -fn http_agent() -> Result { - use std::sync::Arc; - - let mut builder = ureq::builder().try_proxy_from_env(true); - let mut tls_builder = native_tls::TlsConnector::builder(); - if let Some(ca_bundle) = tls_ca_bundle() { - let mut reader = io::BufReader::new(File::open(ca_bundle)?); - for cert in rustls_pemfile::certs(&mut reader) { - let cert = cert?; - tls_builder.add_root_certificate(native_tls::Certificate::from_pem(&cert)?); - } - } - builder = builder.tls_connector(Arc::new(tls_builder.build()?)); - Ok(builder.build()) -} - -#[cfg(feature = "rustls")] -#[allow(clippy::result_large_err)] -fn http_agent() -> Result { - use std::sync::Arc; - - let builder = ureq::builder().try_proxy_from_env(true); - if let Some(ca_bundle) = tls_ca_bundle() { - let mut reader = io::BufReader::new(File::open(ca_bundle)?); - let certs = rustls_pemfile::certs(&mut reader).collect::, _>>()?; - let mut root_certs = rustls::RootCertStore::empty(); - root_certs.add_parsable_certificates(certs); - let client_config = rustls::ClientConfig::builder() - .with_root_certificates(root_certs) - .with_no_client_auth(); - Ok(builder.tls_config(Arc::new(client_config)).build()) - } else { - Ok(builder.build()) - } -} - -#[cfg(not(any(feature = "native-tls", feature = "rustls")))] -#[allow(clippy::result_large_err)] -fn http_agent() -> Result { - let builder = ureq::builder().try_proxy_from_env(true); - Ok(builder.build()) -} - -/// Uploads a single wheel to the registry -#[allow(clippy::result_large_err)] -pub fn upload(registry: &Registry, wheel_path: &Path) -> Result<(), UploadError> { - let hash_hex = hash_file(wheel_path)?; - - let dist = python_pkginfo::Distribution::new(wheel_path) - .map_err(|err| UploadError::PkgInfoError(wheel_path.to_owned(), err))?; - let metadata = dist.metadata(); - - let mut api_metadata = vec![ - (":action", "file_upload".to_string()), - ("sha256_digest", hash_hex), - ("protocol_version", "1".to_string()), - ("metadata_version", metadata.metadata_version.clone()), - ("name", canonicalize_name(&metadata.name)), - ("version", metadata.version.clone()), - ("pyversion", dist.python_version().to_string()), - ("filetype", dist.r#type().to_string()), - ]; - - let mut add_option = |name, value: &Option| { - if let Some(some) = value.clone() { - api_metadata.push((name, some)); - } - }; - - // https://github.com/pypa/warehouse/blob/75061540e6ab5aae3f8758b569e926b6355abea8/warehouse/forklift/legacy.py#L424 - add_option("summary", &metadata.summary); - add_option("description", &metadata.description); - add_option( - "description_content_type", - &metadata.description_content_type, - ); - add_option("author", &metadata.author); - add_option("author_email", &metadata.author_email); - add_option("maintainer", &metadata.maintainer); - add_option("maintainer_email", &metadata.maintainer_email); - add_option("license", &metadata.license); - add_option("keywords", &metadata.keywords); - add_option("home_page", &metadata.home_page); - add_option("download_url", &metadata.download_url); - add_option("requires_python", &metadata.requires_python); - - if metadata.requires_python.is_none() { - // GitLab PyPI repository API implementation requires this metadata field - // and twine always includes it in the request, even when it's empty. - api_metadata.push(("requires_python", "".to_string())); - } - - let mut add_vec = |name, values: &[String]| { - for i in values { - api_metadata.push((name, i.clone())); - } - }; - - add_vec("classifiers", &metadata.classifiers); - add_vec("platform", &metadata.platforms); - add_vec("requires_dist", &metadata.requires_dist); - add_vec("provides_dist", &metadata.provides_dist); - add_vec("obsoletes_dist", &metadata.obsoletes_dist); - add_vec("requires_external", &metadata.requires_external); - add_vec("project_urls", &metadata.project_urls); - - let wheel = File::open(wheel_path)?; - let wheel_name = wheel_path - .file_name() - .expect("Wheel path has a file name") - .to_string_lossy(); - - let mut form = Multipart::new(); - for (key, value) in api_metadata { - form.add_text(key, value); - } - - form.add_stream("content", &wheel, Some(wheel_name), None); - let multipart_data = form.prepare().map_err(|e| e.error)?; - let encoded = STANDARD.encode(format!("{}:{}", registry.username, registry.password)); - - let agent = http_agent()?; - - let response = agent - .post(registry.url.as_str()) - .set( - "Content-Type", - &format!( - "multipart/form-data; boundary={}", - multipart_data.boundary() - ), - ) - .set( - "User-Agent", - &format!("{}/{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")), - ) - .set("Authorization", &format!("Basic {encoded}")) - .send(multipart_data); - - match response { - Ok(_) => Ok(()), - Err(ureq::Error::Status(status, response)) => { - let err_text = response.into_string().unwrap_or_else(|e| { - format!( - "The registry should return some text, \ - even in case of an error, but didn't ({e})" - ) - }); - debug!("Upload error response: {}", err_text); - // Detect FileExistsError the way twine does - // https://github.com/pypa/twine/blob/87846e5777b380d4704704a69e1f9a7a1231451c/twine/commands/upload.py#L30 - if status == 403 { - if err_text.contains("overwrite artifact") { - // Artifactory (https://jfrog.com/artifactory/) - Err(UploadError::FileExistsError(err_text)) - } else { - Err(UploadError::AuthenticationError(err_text)) - } - } else { - let status_string = status.to_string(); - if status == 409 // conflict, pypiserver (https://pypi.org/project/pypiserver) - // PyPI / TestPyPI - || (status == 400 && err_text.contains("already exists")) - // Nexus Repository OSS (https://www.sonatype.com/nexus-repository-oss) - || (status == 400 && err_text.contains("updating asset")) - // # Gitlab Enterprise Edition (https://about.gitlab.com) - || (status == 400 && err_text.contains("already been taken")) - { - Err(UploadError::FileExistsError(err_text)) - } else { - Err(UploadError::StatusCodeError(status_string, err_text)) - } - } - } - Err(err) => Err(UploadError::UreqError(err.into())), - } -} - -/// Handles authentication/keyring integration and retrying of the publish subcommand -pub fn upload_ui(items: &[PathBuf], publish: &PublishOpt) -> Result<()> { - let registry = complete_registry(publish)?; - - eprintln!("🚀 Uploading {} packages", items.len()); - - let title_re = regex::Regex::new(r"(.+?)").unwrap(); - for i in items { - let upload_result = upload(®istry, i); - - match upload_result { - Ok(()) => (), - Err(UploadError::AuthenticationError(msg)) => { - let title = title_re - .captures(&msg) - .and_then(|c| c.get(1)) - .map(|m| m.as_str()); - match title { - Some(title) => { - eprintln!("⛔ {title}"); - } - None => eprintln!("⛔ Username and/or password are wrong"), - } - - #[cfg(feature = "keyring")] - { - // Delete the wrong password from the keyring - let old_username = registry.username; - match keyring::Entry::new(env!("CARGO_PKG_NAME"), &old_username) - .and_then(|keyring| keyring.delete_password()) - { - Ok(()) => { - eprintln!("🔑 Removed wrong password from keyring") - } - Err(keyring::Error::NoEntry) - | Err(keyring::Error::NoStorageAccess(_)) - | Err(keyring::Error::PlatformFailure(_)) => {} - Err(err) => { - eprintln!("⚠️ Warning: Failed to remove password from keyring: {err}") - } - } - } - - bail!("Username and/or password are possibly wrong"); - } - Err(err) => { - let filename = i.file_name().unwrap_or(i.as_os_str()); - if let UploadError::FileExistsError(_) = err { - if publish.skip_existing { - eprintln!( - "⚠️ Note: Skipping {filename:?} because it appears to already exist" - ); - continue; - } - } - let filesize = fs::metadata(i) - .map(|x| ByteSize(x.len()).to_string()) - .unwrap_or_else(|e| format!("Failed to get the filesize of {:?}: {}", &i, e)); - return Err(err).context(format!("💥 Failed to upload {filename:?} ({filesize})")); - } - } - } - - eprintln!("✨ Packages uploaded successfully"); - - #[cfg(feature = "keyring")] - { - // We know the password is correct, so we can save it in the keyring - let username = registry.username.clone(); - let password = registry.password; - match keyring::Entry::new(env!("CARGO_PKG_NAME"), &username) - .and_then(|keyring| keyring.set_password(&password)) - { - Ok(()) - | Err(keyring::Error::NoStorageAccess(_)) - | Err(keyring::Error::PlatformFailure(_)) => {} - Err(err) => { - eprintln!("⚠️ Warning: Failed to store the password in the keyring: {err:?}"); - } - } - } - - Ok(()) -} diff --git a/tests/cli.rs b/tests/cli.rs index 8eeb95dce..873983389 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -4,12 +4,6 @@ fn cli_tests() { t.default_bin_name("maturin"); t.case("tests/cmd/*.toml"); - #[cfg(not(feature = "upload"))] - { - t.skip("tests/cmd/upload.toml"); - t.skip("tests/cmd/publish.toml"); - } - #[cfg(not(feature = "zig"))] { t.skip("tests/cmd/build.toml"); @@ -22,7 +16,7 @@ fn cli_tests() { t.skip("tests/cmd/generate-ci.toml"); } - #[cfg(not(all(feature = "upload", feature = "zig", feature = "scaffolding")))] + #[cfg(not(all(feature = "zig", feature = "scaffolding", not(windows))))] { t.skip("tests/cmd/maturin.toml"); } diff --git a/tests/cmd/maturin.stdout b/tests/cmd/maturin.stdout index 3bdd29c6c..a4d678840 100644 --- a/tests/cmd/maturin.stdout +++ b/tests/cmd/maturin.stdout @@ -1,18 +1,15 @@ -Build and publish crates with pyo3, cffi and uniffi bindings as well as rust binaries as python -packages +Build crates with pyo3, cffi and uniffi bindings as well as rust binaries as python packages -Usage: maturin[EXE] [OPTIONS] +Usage: maturin [OPTIONS] Commands: build Build the crate into python packages - publish Build and publish the crate as python packages to pypi list-python Search and list the available python installations develop Install the crate as module in the current virtualenv sdist Build only a source distribution (sdist) without compiling init Create a new cargo project in an existing directory new Create a new cargo project generate-ci Generate CI configuration - upload Upload python packages to pypi help Print this message or the help of the given subcommand(s) Options: diff --git a/tests/cmd/publish.stderr b/tests/cmd/publish.stderr deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/cmd/publish.stdout b/tests/cmd/publish.stdout deleted file mode 100644 index f5bcc5b43..000000000 --- a/tests/cmd/publish.stdout +++ /dev/null @@ -1,188 +0,0 @@ -Build and publish the crate as python packages to pypi - -Usage: maturin[EXE] publish [OPTIONS] [ARGS]... - -Arguments: - [ARGS]... - Rustc flags - -Options: - --debug - Do not pass --release to cargo - - --no-strip - Do not strip the library for minimum file size - - --no-sdist - Don't build a source distribution - - -r, --repository - The repository (package index) to upload the package to. Should be a section in the config - file. - - Can also be set via MATURIN_REPOSITORY environment variable. - - [env: MATURIN_REPOSITORY=] - [default: pypi] - - --repository-url - The URL of the registry where the wheels are uploaded to. This overrides --repository. - - Can also be set via MATURIN_REPOSITORY_URL environment variable. - - [env: MATURIN_REPOSITORY_URL=] - - -u, --username - Username for pypi or your custom registry. - - Can also be set via MATURIN_USERNAME environment variable. - - Set MATURIN_PYPI_TOKEN variable to use token-based authentication instead - - [env: MATURIN_USERNAME=] - - -p, --password - Password for pypi or your custom registry. - - Can also be set via MATURIN_PASSWORD environment variable. - - [env: MATURIN_PASSWORD] - - --skip-existing - Continue uploading files if one already exists. (Only valid when uploading to PyPI. Other - implementations may not support this.) - - --non-interactive - Do not interactively prompt for username/password if the required credentials are missing. - - Can also be set via MATURIN_NON_INTERACTIVE environment variable. - - [env: MATURIN_NON_INTERACTIVE=] - - --compatibility [...] - Control the platform tag and PyPI compatibility. - - This options offers both fine-grained control over the linux libc tag and a more automatic - PyPI-compatibility option. - - The `pypi` option applies on all platforms and ensure that only tags that can be uploaded - to PyPI are used. The linux-specific options are `manylinux` tags (for example - `manylinux2014`/`manylinux_2_24`) or `musllinux` tags (for example `musllinux_1_2`), and - `linux` for the native linux tag. They are ignored on non-linux platforms. - - Note that `manylinux1` and `manylinux2010` are unsupported by the rust compiler. Wheels - with the native `linux` tag will be rejected by pypi, unless they are separately validated - by `auditwheel`. - - The default is the lowest compatible `manylinux` tag, or plain `linux` if nothing matched. - - -i, --interpreter [...] - The python versions to build wheels for, given as the executables of interpreters such as - `python3.9` or `/usr/bin/python3.8` - - -f, --find-interpreter - Find interpreters from the host machine - - -b, --bindings - Which kind of bindings to use - - [possible values: pyo3, pyo3-ffi, cffi, uniffi, bin] - - -o, --out - The directory to store the built wheels in. Defaults to a new "wheels" directory in the - project's target directory - - --auditwheel - Audit wheel for manylinux compliance - - Possible values: - - repair: Audit and repair wheel for manylinux compliance - - check: Check wheel for manylinux compliance, but do not repair - - skip: Don't check for manylinux compliance - - --zig - For manylinux targets, use zig to ensure compliance for the chosen manylinux version - - Default to manylinux2014/manylinux_2_17 if you do not specify an `--compatibility` - - Make sure you installed zig with `pip install maturin[zig]` - - -q, --quiet - Do not print cargo log messages - - --ignore-rust-version - Ignore `rust-version` specification in packages - - -v, --verbose... - Use verbose output (-vv very verbose/build.rs output) - - --color - Coloring: auto, always, never - - --config - Override a configuration value (unstable) - - -Z - Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details - - --future-incompat-report - Outputs a future incompatibility report at the end of the build (unstable) - - --compression-method - Zip compression method. Only Stored and Deflated are currently compatible with all package - managers - - Possible values: - - deflated: Deflate compression (levels 0-9, default 6) - - stored: No compression - - bzip2: BZIP2 compression (levels 0-9, default 6) - - zstd: Zstandard compression (supported from Python 3.14; levels -7-22, default 3) - - [default: deflated] - - --compression-level - Zip compression level. Defaults to method default - - -h, --help - Print help (see a summary with '-h') - -Compilation Options: - -j, --jobs - Number of parallel jobs, defaults to # of CPUs - - --profile - Build artifacts with the specified Cargo profile - - --target - Build for the target triple - - [env: CARGO_BUILD_TARGET=] - - --target-dir - Directory for all generated artifacts - - --timings= - Timing output formats (unstable) (comma separated): html, json - -Feature Selection: - -F, --features - Space or comma separated list of features to activate - - --all-features - Activate all available features - - --no-default-features - Do not activate the `default` feature - -Manifest Options: - -m, --manifest-path - Path to Cargo.toml - - --frozen - Require Cargo.lock and cache are up to date - - --locked - Require Cargo.lock is up to date - - --offline - Run without accessing the network diff --git a/tests/cmd/publish.toml b/tests/cmd/publish.toml deleted file mode 100644 index 11659172d..000000000 --- a/tests/cmd/publish.toml +++ /dev/null @@ -1,2 +0,0 @@ -bin.name = "maturin" -args = "publish --help" diff --git a/tests/cmd/upload.stderr b/tests/cmd/upload.stderr deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/cmd/upload.stdout b/tests/cmd/upload.stdout deleted file mode 100644 index 86c14e0fd..000000000 --- a/tests/cmd/upload.stdout +++ /dev/null @@ -1,65 +0,0 @@ -Upload python packages to pypi - -It is mostly similar to `twine upload`, but can only upload python wheels and source distributions. - -Usage: maturin[EXE] upload [OPTIONS] [FILE]... - -Arguments: - [FILE]... - The python packages to upload - -Options: - -r, --repository - The repository (package index) to upload the package to. Should be a section in the config - file. - - Can also be set via MATURIN_REPOSITORY environment variable. - - [env: MATURIN_REPOSITORY=] - [default: pypi] - - -v, --verbose... - Use verbose output. - - * Default: Show build information and `cargo build` output. * `-v`: Use `cargo build -v`. - * `-vv`: Show debug logging and use `cargo build -vv`. * `-vvv`: Show trace logging. - - You can configure fine-grained logging using the `RUST_LOG` environment variable. - () - - --repository-url - The URL of the registry where the wheels are uploaded to. This overrides --repository. - - Can also be set via MATURIN_REPOSITORY_URL environment variable. - - [env: MATURIN_REPOSITORY_URL=] - - -u, --username - Username for pypi or your custom registry. - - Can also be set via MATURIN_USERNAME environment variable. - - Set MATURIN_PYPI_TOKEN variable to use token-based authentication instead - - [env: MATURIN_USERNAME=] - - -p, --password - Password for pypi or your custom registry. - - Can also be set via MATURIN_PASSWORD environment variable. - - [env: MATURIN_PASSWORD] - - --skip-existing - Continue uploading files if one already exists. (Only valid when uploading to PyPI. Other - implementations may not support this.) - - --non-interactive - Do not interactively prompt for username/password if the required credentials are missing. - - Can also be set via MATURIN_NON_INTERACTIVE environment variable. - - [env: MATURIN_NON_INTERACTIVE=] - - -h, --help - Print help (see a summary with '-h') diff --git a/tests/cmd/upload.toml b/tests/cmd/upload.toml deleted file mode 100644 index 3c81592c2..000000000 --- a/tests/cmd/upload.toml +++ /dev/null @@ -1,2 +0,0 @@ -bin.name = "maturin" -args = "upload --help"