diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml new file mode 100644 index 00000000000..14e35b7b379 --- /dev/null +++ b/.github/workflows/audit.yml @@ -0,0 +1,30 @@ +name: Security audit + +permissions: + contents: read + +on: + pull_request: + paths: + - '**/Cargo.toml' + - '**/Cargo.lock' + push: + branches: + - master + +jobs: + cargo_deny: + runs-on: ubuntu-latest + strategy: + matrix: + checks: + - advisories + - bans licenses sources + steps: + - uses: actions/checkout@v3 + - uses: EmbarkStudios/cargo-deny-action@v1 + # Prevent sudden announcement of a new advisory from failing ci: + continue-on-error: ${{ matrix.checks == 'advisories' }} + with: + command: check ${{ matrix.checks }} + rust-version: stable diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a1366aff830..400e725e5f2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,6 +12,9 @@ defaults: permissions: contents: read +env: + CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse + jobs: # Check Code style quickly by running `rustfmt` over all code rustfmt: @@ -74,7 +77,7 @@ jobs: os: windows-latest rust: stable-msvc other: i686-pc-windows-msvc - - name: Windows x86_64 gnu nightly + - name: Windows x86_64 gnu nightly # runs out of space while trying to link the test suite os: windows-latest rust: nightly-gnu other: i686-pc-windows-gnu @@ -97,18 +100,18 @@ jobs: - name: Configure extra test environment run: echo CARGO_CONTAINER_TESTS=1 >> $GITHUB_ENV if: matrix.os == 'ubuntu-latest' - - name: Enable sparse - run: echo CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse >> $GITHUB_ENV - if: "!contains(matrix.rust, 'stable')" - run: cargo test - # The testsuite generates a huge amount of data, and fetch-smoke-test was + - name: Clear intermediate test output + run: ci/clean-test-output.sh + - name: gitoxide tests (all git-related tests) + run: cargo test git + env: + __CARGO_USE_GITOXIDE_INSTEAD_OF_GIT2: 1 + # The testsuite generates a huge amount of data, and fetch-smoke-test was # running out of disk space. - name: Clear test output - run: | - df -h - rm -rf target/tmp - df -h + run: ci/clean-test-output.sh - name: Check operability of rustc invocation with argfile env: __CARGO_TEST_FORCE_ARGFILE: 1 @@ -142,10 +145,7 @@ jobs: # The testsuite generates a huge amount of data, and fetch-smoke-test was # running out of disk space. - name: Clear benchmark output - run: | - df -h - rm -rf target/tmp - df -h + run: ci/clean-test-output.sh - name: Fetch smoke test run: ci/fetch-smoke-test.sh @@ -156,10 +156,20 @@ jobs: - run: rustup update stable && rustup default stable - run: cargo test --manifest-path crates/resolver-tests/Cargo.toml + test_gitoxide: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: rustup update --no-self-update stable && rustup default stable + - run: rustup target add i686-unknown-linux-gnu + - run: sudo apt update -y && sudo apt install gcc-multilib libsecret-1-0 libsecret-1-dev -y + - run: rustup component add rustfmt || echo "rustfmt not available" + - run: cargo test + env: + __CARGO_USE_GITOXIDE_INSTEAD_OF_GIT2: 1 + build_std: runs-on: ubuntu-latest - env: - CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse steps: - uses: actions/checkout@v3 - run: rustup update nightly && rustup default nightly @@ -196,7 +206,7 @@ jobs: permissions: contents: none name: bors build finished - needs: [docs, rustfmt, test, resolver, build_std] + needs: [docs, rustfmt, test, resolver, build_std, test_gitoxide] runs-on: ubuntu-latest if: "success() && github.event_name == 'push' && github.ref == 'refs/heads/auto-cargo'" steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index 237bbb809b4..a3d488d9249 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,79 @@ # Changelog +## Cargo 1.70 (2023-06-01) +[9880b408...HEAD](https://github.com/rust-lang/cargo/compare/9880b408...HEAD) + +### Added + +- The `CARGO_PKG_README` environment variable is now set to the path to the + README file when compiling a crate. + [#11645](https://github.com/rust-lang/cargo/pull/11645) +- Cargo now displays richer information of Cargo target failed to compile. + [#11636](https://github.com/rust-lang/cargo/pull/11636) + +### Changed + +- 🎉 The `sparse` protocol is now the default protocol for crates.io! + ([RFC 2789](https://github.com/rust-lang/rfcs/blob/master/text/2789-sparse-index.md)) + ([docs](https://doc.rust-lang.org/nightly/cargo/reference/registries.html#registry-protocols)) + [#11791](https://github.com/rust-lang/cargo/pull/11791) + [#11783](https://github.com/rust-lang/cargo/pull/11783) + +### Fixed + +- Removed duplicates of possible values in `--charset` option of `cargo tree`. + [#11785](https://github.com/rust-lang/cargo/pull/11785) +- Fixed `CARGO_CFG_` vars for configs defined both with and without value. + [#11790](https://github.com/rust-lang/cargo/pull/11790) +- Broke endless loop on cyclic features in added dependency in `cargo add`. + [#11805](https://github.com/rust-lang/cargo/pull/11805) +- Don't panic when [`patch`] involved in dependency resolution results in a conflict. + [#11770](https://github.com/rust-lang/cargo/pull/11770) + +### Nightly only + +- Added `-Zdirect-minimal-versions`. This behaves like `-Zminimal-versions` but + only for direct dependencies. + ([docs](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#direct-minimal-versions)) + [#11688](https://github.com/rust-lang/cargo/pull/11688) +- Added `-Zgitoxide` which switches all `git fetch` operation in Cargo to + use `gitoxide` crate. This is still an MVP but could improve the performance + up to 2 times. + ([docs](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html##gitoxide)) + [#11448](https://github.com/rust-lang/cargo/pull/11448) + [#11800](https://github.com/rust-lang/cargo/pull/11800) +- Removed `-Zjobserver-per-rustc`. Its rustc counterpart never got landed. + [#11764](https://github.com/rust-lang/cargo/pull/11764) + +### Documentation + +- Cleaned-up unstable documentation. + [#11793](https://github.com/rust-lang/cargo/pull/11793) +- Enhanced the documentation of timing report with graphs. + [#11798](https://github.com/rust-lang/cargo/pull/11798) + +### Internal + +- Switched to `sha2` crate for SHA256 calculation. + [#11795](https://github.com/rust-lang/cargo/pull/11795) + [#11807](https://github.com/rust-lang/cargo/pull/11807) +- Updated to `base64` v0.21.0. + [#11796](https://github.com/rust-lang/cargo/pull/11796) +- Integrated `cargo-deny` in Cargo its own CI pipeline. + [#11761](https://github.com/rust-lang/cargo/pull/11761) + ## Cargo 1.69 (2023-04-20) -[985d561f...HEAD](https://github.com/rust-lang/cargo/compare/985d561f...HEAD) +[985d561f...rust-1.69.0](https://github.com/rust-lang/cargo/compare/985d561f...rust-1.69.0) ### Added - Cargo now suggests `cargo fix` or `cargo clippy --fix` - when compilation warnings/errors can be auto-fixed. + when compilation warnings are auto-fixable. [#11558](https://github.com/rust-lang/cargo/pull/11558) +- Cargo now suggests `cargo add` if you try to install a library crate. + [#11410](https://github.com/rust-lang/cargo/pull/11410) +- Cargo now sets `CARGO_BIN_NAME` environment variable also for binary examples. + [#11705](https://github.com/rust-lang/cargo/pull/11705) ### Changed @@ -15,26 +81,145 @@ and an inherited dependency of a member has `default-features = true`, Cargo will enable default features of that dependency. [#11409](https://github.com/rust-lang/cargo/pull/11409) +- ❗ Deny `CARGO_HOME` in `[env]` configuration table. Cargo itself doesn't + pick up this value, but recursive calls to cargo will. We consider it as a + wrong behavior to only pass it to recursive invocations. + [#11644](https://github.com/rust-lang/cargo/pull/11644) +- ❗ Debuginfo for build dependencies is now off if not explicit set. This is + expected to boost the overall build time. + [#11252](https://github.com/rust-lang/cargo/pull/11252) +- Cargo now emits errors on invalid alphanumeric token for crates.io. + [#11600](https://github.com/rust-lang/cargo/pull/11600) - `cargo add` now checks only the order of `[dependencies]` without considering `[dependencies.*]`. [#11612](https://github.com/rust-lang/cargo/pull/11612) -- Several documentation improvements. - [#11576](https://github.com/rust-lang/cargo/pull/11576) - [#11604](https://github.com/rust-lang/cargo/pull/11604) - [#11620](https://github.com/rust-lang/cargo/pull/11620) - [#11603](https://github.com/rust-lang/cargo/pull/11603) +- Cargo now respects the new jobserver IPC style in GNU Make 4.4, by updating + its dependency `jobserver`. + [#11767](https://github.com/rust-lang/cargo/pull/11767) +- `cargo install` now reports required features when no binary meets its requirements. + [#11647](https://github.com/rust-lang/cargo/pull/11647) ### Fixed +- Uplifted `.dwp` DWARF package file next to the executable for debuggers to + locate them. + [#11572](https://github.com/rust-lang/cargo/pull/11572) - Fixed build scripts triggering recompiles when a `rerun-if-changed` points to a directory whose mtime is not preserved by the filesystem. [#11613](https://github.com/rust-lang/cargo/pull/11613) - Fixed panics when using dependencies from `[workspace.dependencies]` for `[patch]`. This usage is not supposed to be supported. [#11565](https://github.com/rust-lang/cargo/pull/11565) + [#11630](https://github.com/rust-lang/cargo/pull/11630) +- Fixed `cargo report` saving the same future-incompat reports multiple times. + [#11648](https://github.com/rust-lang/cargo/pull/11648) +- Fixed the incorrect inference of a directory ending with `.rs` as a file. + [#11678](https://github.com/rust-lang/cargo/pull/11678) +- Fixed `.cargo-ok` file being truncated wrongly, preventing from using a dependency. + [#11665](https://github.com/rust-lang/cargo/pull/11665) + [#11724](https://github.com/rust-lang/cargo/pull/11724) ### Nightly only +- `-Zrustdoc-scrape-example` must fail with bad build script. + [#11694](https://github.com/rust-lang/cargo/pull/11694) +- Updated 1password credential manager integration to the version 2 CLI. + [#11692](https://github.com/rust-lang/cargo/pull/11692) +- Emit an error message for transitive artifact dependencies with targets the + package doesn't directly interact with. + [#11643](https://github.com/rust-lang/cargo/pull/11643) +- Added `-C` flag for changing current dir before build starts. + [#10952](https://github.com/rust-lang/cargo/pull/10952) + +### Documentation + +- Clarified the difference between `CARGO_CRATE_NAME` and `CARGO_PKG_NAME`. + [#11576](https://github.com/rust-lang/cargo/pull/11576) +- Added links to the Target section of the glossary for occurences of target triple. + [#11603](https://github.com/rust-lang/cargo/pull/11603) +- Described how the current resolver sometimes duplicates depenencies. + [#11604](https://github.com/rust-lang/cargo/pull/11604) +- Added a note about verifying your email address on crates.io. + [#11620](https://github.com/rust-lang/cargo/pull/11620) +- Mention current default value in `publish.timeout` docs. + [#11652](https://github.com/rust-lang/cargo/pull/11652) +- More doc comments for `cargo::core::compiler` modules. + [#11669](https://github.com/rust-lang/cargo/pull/11669) + [#11703](https://github.com/rust-lang/cargo/pull/11703) + [#11711](https://github.com/rust-lang/cargo/pull/11711) + [#11758](https://github.com/rust-lang/cargo/pull/11758) +- Added more guidance on how to implement unstable features. + [#11675](https://github.com/rust-lang/cargo/pull/11675) +- Fixed unstable chapter layout for `codegen-backend`. + [#11676](https://github.com/rust-lang/cargo/pull/11676) +- Add a link to LTO doc. + [#11701](https://github.com/rust-lang/cargo/pull/11701) +- Added documentation for the configuration discovery of `cargo install` + to the man pages + [#11763](https://github.com/rust-lang/cargo/pull/11763) +- Documented `-F` flag as an alias for `--features` in `cargo add`. + [#11774](https://github.com/rust-lang/cargo/pull/11774) + +### Internal + +- Disable network SSH tests on Windows. + [#11610](https://github.com/rust-lang/cargo/pull/11610) +- Made some blocking tests non-blocking. + [#11650](https://github.com/rust-lang/cargo/pull/11650) +- Deny warnings in CI, not locally. + [#11699](https://github.com/rust-lang/cargo/pull/11699) +- Re-export `cargo_new::NewProjectKind` as public. + [#11700](https://github.com/rust-lang/cargo/pull/11700) +- Made dependencies in alphabetical order. + [#11719](https://github.com/rust-lang/cargo/pull/11719) +- Switched some tests from `build` to `check`. + [#11725](https://github.com/rust-lang/cargo/pull/11725) +- Consolidated how Cargo reads environments variables internally. + [#11727](https://github.com/rust-lang/cargo/pull/11727) + [#11754](https://github.com/rust-lang/cargo/pull/11754) +- Fixed tests with nondeterministic ordering + [#11766](https://github.com/rust-lang/cargo/pull/11766) +- Added a test to verify the intermediate artifacts persist in the temp directory. + [#11771](https://github.com/rust-lang/cargo/pull/11771) +- Updated cross test instructions for aarch64-apple-darwin. + [#11663](https://github.com/rust-lang/cargo/pull/11663) +- Updated to `toml` v0.6 and `toml_edit` v0.18 for TOML manipulations. + [#11618](https://github.com/rust-lang/cargo/pull/11618) +- Updated to `clap` v4.1.3. + [#11619](https://github.com/rust-lang/cargo/pull/11619) +- Replaced `winapi` with `windows-sys` crate for Windows bindings. + [#11656](https://github.com/rust-lang/cargo/pull/11656) +- Reused `url` crate for percent encoding instead of `percent-encoding`. + [#11750](https://github.com/rust-lang/cargo/pull/11750) +- Cargo contributors can benefit from smart punctuations when writing + documentations, e.g., `---` is auto-converted into an em dash. + ([docs](https://rust-lang.github.io/mdBook/format/markdown.html#smart-punctuation)) + [#11646](https://github.com/rust-lang/cargo/pull/11646) + [#11715](https://github.com/rust-lang/cargo/pull/11715) +- Cargo's CI pipeline now covers macOS on nightly. + [#11712](https://github.com/rust-lang/cargo/pull/11712) +- Re-enabled some clippy lints in Cargo itself. + [#11722](https://github.com/rust-lang/cargo/pull/11722) +- Enabled sparse protocol in Cargo's CI. + [#11632](https://github.com/rust-lang/cargo/pull/11632) +- Pull requests in Cargo now get autolabelled for label `A-*` and `Command-*`. + [#11664](https://github.com/rust-lang/cargo/pull/11664) + [#11679](https://github.com/rust-lang/cargo/pull/11679) + +## Cargo 1.68.2 (2023-03-28) +[115f3455...rust-1.68.0](https://github.com/rust-lang/cargo/compare/115f3455...rust-1.68.0) + +- Updated the GitHub RSA SSH host key bundled within cargo. + The key was [rotated by + GitHub](https://github.blog/2023-03-23-we-updated-our-rsa-ssh-host-key/) on + 2023-03-24 after the old one leaked. + [#11883](https://github.com/rust-lang/cargo/pull/11883) +- Added support for SSH known hosts marker `@revoked`. + [#11635](https://github.com/rust-lang/cargo/pull/11635) +- Marked the old GitHub RSA host key as revoked. This will prevent Cargo from + accepting the leaked key even when trusted by the system. + [#11889](https://github.com/rust-lang/cargo/pull/11889) + ## Cargo 1.68 (2023-03-09) [f6e737b1...rust-1.68.0](https://github.com/rust-lang/cargo/compare/f6e737b1...rust-1.68.0) @@ -46,6 +231,8 @@ ([docs](https://doc.rust-lang.org/nightly/cargo/reference/registries.html#registry-protocols)) [#11224](https://github.com/rust-lang/cargo/pull/11224) [#11480](https://github.com/rust-lang/cargo/pull/11480) + [#11733](https://github.com/rust-lang/cargo/pull/11733) + [#11756](https://github.com/rust-lang/cargo/pull/11756) - 🎉 `home` crate is now a subcrate in `rust-lang/cargo` repository. Welcome! [#11359](https://github.com/rust-lang/cargo/pull/11359) [#11481](https://github.com/rust-lang/cargo/pull/11481) @@ -104,6 +291,8 @@ which previously made Cargo fail to compile on certain platforms. [#11347](https://github.com/rust-lang/cargo/pull/11347) [#11633](https://github.com/rust-lang/cargo/pull/11633) +- Don't panic in Windows headless session with really long file names. + [#11759](https://github.com/rust-lang/cargo/pull/11759) ### Nightly only diff --git a/Cargo.toml b/Cargo.toml index c6d470a2f52..56c9827cbfb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo" -version = "0.70.0" +version = "0.71.0" edition = "2021" license = "MIT OR Apache-2.0" homepage = "https://crates.io" @@ -17,19 +17,21 @@ path = "src/cargo/lib.rs" [dependencies] anyhow = "1.0.47" -base64 = "0.13.1" +base64 = "0.21.0" bytesize = "1.0" cargo-platform = { path = "crates/cargo-platform", version = "0.1.2" } -cargo-util = { path = "crates/cargo-util", version = "0.2.3" } -clap = "4.1.3" -crates-io = { path = "crates/crates-io", version = "0.35.1" } +cargo-util = { path = "crates/cargo-util", version = "0.2.4" } +clap = "4.2.0" +crates-io = { path = "crates/crates-io", version = "0.36.0" } curl = { version = "0.4.44", features = ["http2"] } -curl-sys = "0.4.59" +curl-sys = "0.4.61" env_logger = "0.10.0" filetime = "0.2.9" flate2 = { version = "1.0.3", default-features = false, features = ["zlib"] } -git2 = "0.16.0" -git2-curl = "0.17.0" +git2 = "0.17.0" +git2-curl = "0.18.0" +gix = { version = "0.39.0", default-features = false, features = ["blocking-http-transport-curl", "progress-tree"] } +gix-features-for-configuration-only = { version = "0.28.0", package = "gix-features", features = [ "parallel" ] } glob = "0.3.0" hex = "0.4" hmac = "0.12.1" @@ -45,9 +47,7 @@ jobserver = "0.1.26" lazy_static = "1.2.0" lazycell = "1.2.0" libc = "0.2" -# Temporarily pin libgit2-sys due to some issues with SSH not working on -# Windows. -libgit2-sys = "=0.14.1" +libgit2-sys = "0.15.0" log = "0.4.6" memchr = "2.1.3" opener = "0.5" @@ -56,6 +56,7 @@ os_info = "3.5.0" pasetors = { version = "0.6.4", features = ["v3", "paserk", "std", "serde"] } pathdiff = "0.2" pretty_env_logger = { version = "0.4", optional = true } +rand = "0.8.5" rustfix = "0.6.0" semver = { version = "1.0.3", features = ["serde"] } serde = { version = "1.0.123", features = ["derive"] } @@ -89,6 +90,7 @@ version = "0.45" features = [ "Win32_Foundation", "Win32_Storage_FileSystem", + "Win32_System_Console", "Win32_System_IO", "Win32_System_Threading", "Win32_System_JobObjects", diff --git a/benches/benchsuite/src/lib.rs b/benches/benchsuite/src/lib.rs index 42e0f513c26..e470a03b953 100644 --- a/benches/benchsuite/src/lib.rs +++ b/benches/benchsuite/src/lib.rs @@ -120,7 +120,7 @@ impl Fixtures { } else { fs::create_dir_all(&index).unwrap(); git("init --bare"); - git("remote add origin https://github.com/rust-lang/crates.io-index"); + git("remote add origin https://github.com/rust-lang/crates.io-index-archive"); } git(&format!("fetch origin {}", CRATES_IO_COMMIT)); git("branch -f master FETCH_HEAD"); diff --git a/build.rs b/build.rs index 13e88935e09..752221f8cdc 100644 --- a/build.rs +++ b/build.rs @@ -7,13 +7,15 @@ use std::process::Command; fn main() { commit_info(); compress_man(); - println!( - "cargo:rustc-env=RUST_HOST_TARGET={}", - std::env::var("TARGET").unwrap() - ); + // ALLOWED: Accessing environment during build time shouldn't be prohibited. + #[allow(clippy::disallowed_methods)] + let target = std::env::var("TARGET").unwrap(); + println!("cargo:rustc-env=RUST_HOST_TARGET={target}"); } fn compress_man() { + // ALLOWED: Accessing environment during build time shouldn't be prohibited. + #[allow(clippy::disallowed_methods)] let out_path = Path::new(&std::env::var("OUT_DIR").unwrap()).join("man.tgz"); let dst = fs::File::create(out_path).unwrap(); let encoder = GzBuilder::new() diff --git a/ci/clean-test-output.sh b/ci/clean-test-output.sh new file mode 100755 index 00000000000..f1f2ec61cf5 --- /dev/null +++ b/ci/clean-test-output.sh @@ -0,0 +1,8 @@ +#!/bin/bash +# This script remove test and benchmark output and displays disk usage. + +set -euo pipefail + +df -h +rm -rf target/tmp +df -h diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 00000000000..4f9be8f9b08 --- /dev/null +++ b/clippy.toml @@ -0,0 +1,6 @@ +disallowed-methods = [ + { path = "std::env::var", reason = "Use `Config::get_env` instead. See rust-lang/cargo#11588" }, + { path = "std::env::var_os", reason = "Use `Config::get_env_os` instead. See rust-lang/cargo#11588" }, + { path = "std::env::vars", reason = "Not recommended to use in Cargo. See rust-lang/cargo#11588" }, + { path = "std::env::vars_os", reason = "Not recommended to use in Cargo. See rust-lang/cargo#11588" }, +] diff --git a/crates/cargo-platform/Cargo.toml b/crates/cargo-platform/Cargo.toml index 9a311701286..a5e51ee5de3 100644 --- a/crates/cargo-platform/Cargo.toml +++ b/crates/cargo-platform/Cargo.toml @@ -9,4 +9,4 @@ documentation = "https://docs.rs/cargo-platform" description = "Cargo's representation of a target platform." [dependencies] -serde = { version = "1.0.82", features = ['derive'] } +serde = "1.0.82" diff --git a/crates/cargo-test-support/Cargo.toml b/crates/cargo-test-support/Cargo.toml index 6c4b2518255..91e6e4e34c1 100644 --- a/crates/cargo-test-support/Cargo.toml +++ b/crates/cargo-test-support/Cargo.toml @@ -14,7 +14,7 @@ cargo-util = { path = "../cargo-util" } crates-io = { path = "../crates-io" } filetime = "0.2" flate2 = { version = "1.0", default-features = false, features = ["zlib"] } -git2 = "0.16.0" +git2 = "0.17.0" glob = "0.3" itertools = "0.10.0" lazy_static = "1.0" diff --git a/crates/cargo-test-support/src/compare.rs b/crates/cargo-test-support/src/compare.rs index da1d0992c7a..96ce52afcd7 100644 --- a/crates/cargo-test-support/src/compare.rs +++ b/crates/cargo-test-support/src/compare.rs @@ -192,6 +192,7 @@ fn substitute_macros(input: &str) -> String { ("[CHECKING]", " Checking"), ("[COMPLETED]", " Completed"), ("[CREATED]", " Created"), + ("[DOWNGRADING]", " Downgrading"), ("[FINISHED]", " Finished"), ("[ERROR]", "error:"), ("[WARNING]", "warning:"), @@ -210,6 +211,7 @@ fn substitute_macros(input: &str) -> String { ("[DOWNLOADING]", " Downloading"), ("[DOWNLOADED]", " Downloaded"), ("[UPLOADING]", " Uploading"), + ("[UPLOADED]", " Uploaded"), ("[VERIFYING]", " Verifying"), ("[ARCHIVING]", " Archiving"), ("[INSTALLING]", " Installing"), @@ -231,6 +233,7 @@ fn substitute_macros(input: &str) -> String { ("[EXECUTABLE]", " Executable"), ("[SKIPPING]", " Skipping"), ("[WAITING]", " Waiting"), + ("[PUBLISHED]", " Published"), ]; let mut result = input.to_owned(); for &(pat, subst) in ¯os { diff --git a/crates/cargo-test-support/src/git.rs b/crates/cargo-test-support/src/git.rs index 18c4646b37d..6fde9646736 100644 --- a/crates/cargo-test-support/src/git.rs +++ b/crates/cargo-test-support/src/git.rs @@ -247,3 +247,10 @@ pub fn tag(repo: &git2::Repository, name: &str) { false )); } + +/// Returns true if gitoxide is globally activated. +/// +/// That way, tests that normally use `git2` can transparently use `gitoxide`. +pub fn cargo_uses_gitoxide() -> bool { + std::env::var_os("__CARGO_USE_GITOXIDE_INSTEAD_OF_GIT2").map_or(false, |value| value == "1") +} diff --git a/crates/cargo-test-support/src/publish.rs b/crates/cargo-test-support/src/publish.rs index 85bc93cbdf7..64774bc43c8 100644 --- a/crates/cargo-test-support/src/publish.rs +++ b/crates/cargo-test-support/src/publish.rs @@ -189,7 +189,7 @@ pub(crate) fn create_index_line( json.to_string() } -pub(crate) fn write_to_index(registry_path: &PathBuf, name: &str, line: String, local: bool) { +pub(crate) fn write_to_index(registry_path: &Path, name: &str, line: String, local: bool) { let file = cargo_util::registry::make_dep_path(name, false); // Write file/line in the index. diff --git a/crates/cargo-test-support/src/registry.rs b/crates/cargo-test-support/src/registry.rs index 7b1dc541a42..5faf2354045 100644 --- a/crates/cargo-test-support/src/registry.rs +++ b/crates/cargo-test-support/src/registry.rs @@ -13,7 +13,7 @@ use std::fmt; use std::fs::{self, File}; use std::io::{BufRead, BufReader, Read, Write}; use std::net::{SocketAddr, TcpListener, TcpStream}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::thread::{self, JoinHandle}; use tar::{Builder, Header}; use time::format_description::well_known::Rfc3339; @@ -78,6 +78,8 @@ impl Token { } } +type RequestCallback = Box Response>; + /// A builder for initializing registries. pub struct RegistryBuilder { /// If set, configures an alternate registry with the given name. @@ -97,7 +99,11 @@ pub struct RegistryBuilder { /// Write the registry in configuration. configure_registry: bool, /// API responders. - custom_responders: HashMap<&'static str, Box Response>>, + custom_responders: HashMap, + /// Handler for 404 responses. + not_found_handler: RequestCallback, + /// If nonzero, the git index update to be delayed by the given number of seconds. + delayed_index_update: usize, } pub struct TestRegistry { @@ -147,6 +153,13 @@ impl TestRegistry { impl RegistryBuilder { #[must_use] pub fn new() -> RegistryBuilder { + let not_found = |_req: &Request, _server: &HttpServer| -> Response { + Response { + code: 404, + headers: vec![], + body: b"not found".to_vec(), + } + }; RegistryBuilder { alternative: None, token: None, @@ -157,6 +170,8 @@ impl RegistryBuilder { configure_registry: true, configure_token: true, custom_responders: HashMap::new(), + not_found_handler: Box::new(not_found), + delayed_index_update: 0, } } @@ -164,10 +179,27 @@ impl RegistryBuilder { #[must_use] pub fn add_responder Response>( mut self, - url: &'static str, + url: impl Into, responder: R, ) -> Self { - self.custom_responders.insert(url, Box::new(responder)); + self.custom_responders + .insert(url.into(), Box::new(responder)); + self + } + + #[must_use] + pub fn not_found_handler Response>( + mut self, + responder: R, + ) -> Self { + self.not_found_handler = Box::new(responder); + self + } + + /// Configures the git index update to be delayed by the given number of seconds. + #[must_use] + pub fn delayed_index_update(mut self, delay: usize) -> Self { + self.delayed_index_update = delay; self } @@ -265,6 +297,8 @@ impl RegistryBuilder { token.clone(), self.auth_required, self.custom_responders, + self.not_found_handler, + self.delayed_index_update, ); let index_url = if self.http_index { server.index_url() @@ -590,16 +624,18 @@ pub struct HttpServer { addr: SocketAddr, token: Token, auth_required: bool, - custom_responders: HashMap<&'static str, Box Response>>, + custom_responders: HashMap, + not_found_handler: RequestCallback, + delayed_index_update: usize, } -/// A helper struct that collects the arguments for [HttpServer::check_authorized]. +/// A helper struct that collects the arguments for [`HttpServer::check_authorized`]. /// Based on looking at the request, these are the fields that the authentication header should attest to. -pub struct Mutation<'a> { - pub mutation: &'a str, - pub name: Option<&'a str>, - pub vers: Option<&'a str>, - pub cksum: Option<&'a str>, +struct Mutation<'a> { + mutation: &'a str, + name: Option<&'a str>, + vers: Option<&'a str>, + cksum: Option<&'a str>, } impl HttpServer { @@ -609,10 +645,9 @@ impl HttpServer { api_path: PathBuf, token: Token, auth_required: bool, - api_responders: HashMap< - &'static str, - Box Response>, - >, + custom_responders: HashMap, + not_found_handler: RequestCallback, + delayed_index_update: usize, ) -> HttpServerHandle { let listener = TcpListener::bind("127.0.0.1:0").unwrap(); let addr = listener.local_addr().unwrap(); @@ -624,7 +659,9 @@ impl HttpServer { addr, token, auth_required, - custom_responders: api_responders, + custom_responders, + not_found_handler, + delayed_index_update, }; let handle = Some(thread::spawn(move || server.start())); HttpServerHandle { addr, handle } @@ -916,12 +953,8 @@ impl HttpServer { } /// Not found response - pub fn not_found(&self, _req: &Request) -> Response { - Response { - code: 404, - headers: vec![], - body: b"not found".to_vec(), - } + pub fn not_found(&self, req: &Request) -> Response { + (self.not_found_handler)(req, self) } /// Respond OK without doing anything @@ -1040,49 +1073,23 @@ impl HttpServer { return self.unauthorized(req); } - // Write the `.crate` let dst = self .dl_path .join(&new_crate.name) .join(&new_crate.vers) .join("download"); - t!(fs::create_dir_all(dst.parent().unwrap())); - t!(fs::write(&dst, file)); - - let deps = new_crate - .deps - .iter() - .map(|dep| { - let (name, package) = match &dep.explicit_name_in_toml { - Some(explicit) => (explicit.to_string(), Some(dep.name.to_string())), - None => (dep.name.to_string(), None), - }; - serde_json::json!({ - "name": name, - "req": dep.version_req, - "features": dep.features, - "default_features": true, - "target": dep.target, - "optional": dep.optional, - "kind": dep.kind, - "registry": dep.registry, - "package": package, - }) - }) - .collect::>(); - - let line = create_index_line( - serde_json::json!(new_crate.name), - &new_crate.vers, - deps, - &file_cksum, - new_crate.features, - false, - new_crate.links, - None, - ); - write_to_index(&self.registry_path, &new_crate.name, line, false); + if self.delayed_index_update == 0 { + save_new_crate(dst, new_crate, file, file_cksum, &self.registry_path); + } else { + let delayed_index_update = self.delayed_index_update; + let registry_path = self.registry_path.clone(); + let file = Vec::from(file); + thread::spawn(move || { + thread::sleep(std::time::Duration::new(delayed_index_update as u64, 0)); + save_new_crate(dst, new_crate, &file, file_cksum, ®istry_path); + }); + } self.ok(&req) } else { @@ -1095,6 +1102,53 @@ impl HttpServer { } } +fn save_new_crate( + dst: PathBuf, + new_crate: crates_io::NewCrate, + file: &[u8], + file_cksum: String, + registry_path: &Path, +) { + // Write the `.crate` + t!(fs::create_dir_all(dst.parent().unwrap())); + t!(fs::write(&dst, file)); + + let deps = new_crate + .deps + .iter() + .map(|dep| { + let (name, package) = match &dep.explicit_name_in_toml { + Some(explicit) => (explicit.to_string(), Some(dep.name.to_string())), + None => (dep.name.to_string(), None), + }; + serde_json::json!({ + "name": name, + "req": dep.version_req, + "features": dep.features, + "default_features": true, + "target": dep.target, + "optional": dep.optional, + "kind": dep.kind, + "registry": dep.registry, + "package": package, + }) + }) + .collect::>(); + + let line = create_index_line( + serde_json::json!(new_crate.name), + &new_crate.vers, + deps, + &file_cksum, + new_crate.features, + false, + new_crate.links, + None, + ); + + write_to_index(registry_path, &new_crate.name, line, false); +} + impl Package { /// Creates a new package builder. /// Call `publish()` to finalize and build the package. @@ -1204,7 +1258,7 @@ impl Package { } /// Adds a platform-specific dependency. Example: - /// ``` + /// ```toml /// [target.'cfg(windows)'.dependencies] /// foo = {version = "1.0"} /// ``` diff --git a/crates/cargo-util/Cargo.toml b/crates/cargo-util/Cargo.toml index aa25c137640..7427ceb1a8e 100644 --- a/crates/cargo-util/Cargo.toml +++ b/crates/cargo-util/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo-util" -version = "0.2.3" +version = "0.2.4" edition = "2021" license = "MIT OR Apache-2.0" homepage = "https://github.com/rust-lang/cargo" @@ -9,7 +9,7 @@ description = "Miscellaneous support code used by Cargo." [dependencies] anyhow = "1.0.34" -crypto-hash = "0.3.1" +sha2 = "0.10.6" filetime = "0.2.9" hex = "0.4.2" jobserver = "0.1.26" diff --git a/crates/cargo-util/src/read2.rs b/crates/cargo-util/src/read2.rs index 742dc1daa60..2e6b66a14ff 100644 --- a/crates/cargo-util/src/read2.rs +++ b/crates/cargo-util/src/read2.rs @@ -2,21 +2,28 @@ pub use self::imp::read2; #[cfg(unix)] mod imp { + use libc::{c_int, fcntl, F_GETFL, F_SETFL, O_NONBLOCK}; use std::io; use std::io::prelude::*; use std::mem; use std::os::unix::prelude::*; use std::process::{ChildStderr, ChildStdout}; + fn set_nonblock(fd: c_int) -> io::Result<()> { + let flags = unsafe { fcntl(fd, F_GETFL) }; + if flags == -1 || unsafe { fcntl(fd, F_SETFL, flags | O_NONBLOCK) } == -1 { + return Err(io::Error::last_os_error()); + } + Ok(()) + } + pub fn read2( mut out_pipe: ChildStdout, mut err_pipe: ChildStderr, data: &mut dyn FnMut(bool, &mut Vec, bool), ) -> io::Result<()> { - unsafe { - libc::fcntl(out_pipe.as_raw_fd(), libc::F_SETFL, libc::O_NONBLOCK); - libc::fcntl(err_pipe.as_raw_fd(), libc::F_SETFL, libc::O_NONBLOCK); - } + set_nonblock(out_pipe.as_raw_fd())?; + set_nonblock(err_pipe.as_raw_fd())?; let mut out_done = false; let mut err_done = false; @@ -32,7 +39,7 @@ mod imp { let mut errfd = 1; while nfds > 0 { - // wait for either pipe to become readable using `select` + // wait for either pipe to become readable using `poll` let r = unsafe { libc::poll(fds.as_mut_ptr(), nfds, -1) }; if r == -1 { let err = io::Error::last_os_error(); diff --git a/crates/cargo-util/src/sha256.rs b/crates/cargo-util/src/sha256.rs index 58821f43f7a..8906fe93d79 100644 --- a/crates/cargo-util/src/sha256.rs +++ b/crates/cargo-util/src/sha256.rs @@ -1,20 +1,20 @@ use super::paths; use anyhow::{Context, Result}; -use crypto_hash::{Algorithm, Hasher}; +use sha2::{Digest, Sha256 as Sha2_sha256}; use std::fs::File; -use std::io::{self, Read, Write}; +use std::io::{self, Read}; use std::path::Path; -pub struct Sha256(Hasher); +pub struct Sha256(Sha2_sha256); impl Sha256 { pub fn new() -> Sha256 { - let hasher = Hasher::new(Algorithm::SHA256); + let hasher = Sha2_sha256::new(); Sha256(hasher) } pub fn update(&mut self, bytes: &[u8]) -> &mut Sha256 { - let _ = self.0.write_all(bytes); + let _ = self.0.update(bytes); self } @@ -38,10 +38,7 @@ impl Sha256 { } pub fn finish(&mut self) -> [u8; 32] { - let mut ret = [0u8; 32]; - let data = self.0.finish(); - ret.copy_from_slice(&data[..]); - ret + self.0.finalize_reset().into() } pub fn finish_hex(&mut self) -> String { diff --git a/crates/crates-io/Cargo.toml b/crates/crates-io/Cargo.toml index 29c9e4b6487..004e2daff9e 100644 --- a/crates/crates-io/Cargo.toml +++ b/crates/crates-io/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "crates-io" -version = "0.35.1" +version = "0.36.0" edition = "2021" license = "MIT OR Apache-2.0" repository = "https://github.com/rust-lang/cargo" diff --git a/crates/crates-io/lib.rs b/crates/crates-io/lib.rs index ad3ea76763d..e0197568a6c 100644 --- a/crates/crates-io/lib.rs +++ b/crates/crates-io/lib.rs @@ -219,6 +219,15 @@ impl Registry { self.token = token; } + fn token(&self) -> Result<&str> { + let token = match self.token.as_ref() { + Some(s) => s, + None => bail!("no upload token found, please run `cargo login`"), + }; + check_token(token)?; + Ok(token) + } + pub fn host(&self) -> &str { &self.host } @@ -278,16 +287,12 @@ impl Registry { let url = format!("{}/api/v1/crates/new", self.host); - let token = match self.token.as_ref() { - Some(s) => s, - None => bail!("no upload token found, please run `cargo login`"), - }; self.handle.put(true)?; self.handle.url(&url)?; self.handle.in_filesize(size as u64)?; let mut headers = List::new(); headers.append("Accept: application/json")?; - headers.append(&format!("Authorization: {}", token))?; + headers.append(&format!("Authorization: {}", self.token()?))?; self.handle.http_headers(headers)?; let started = Instant::now(); @@ -390,12 +395,7 @@ impl Registry { headers.append("Content-Type: application/json")?; if self.auth_required || authorized == Auth::Authorized { - let token = match self.token.as_ref() { - Some(s) => s, - None => bail!("no upload token found, please run `cargo login`"), - }; - check_token(token)?; - headers.append(&format!("Authorization: {}", token))?; + headers.append(&format!("Authorization: {}", self.token()?))?; } self.handle.http_headers(headers)?; match body { @@ -522,10 +522,11 @@ pub fn check_token(token: &str) -> Result<()> { bail!("please provide a non-empty token"); } if token.bytes().all(|b| { - b >= 32 // undefined in ISO-8859-1, in ASCII/ UTF-8 not-printable character - && b < 128 // utf-8: the first bit signals a multi-byte character - && b != 127 // 127 is a control character in ascii and not in ISO 8859-1 - || b == b't' // tab is also allowed (even when < 32) + // This is essentially the US-ASCII limitation of + // https://www.rfc-editor.org/rfc/rfc9110#name-field-values. That is, + // visible ASCII characters (0x21-0x7e), space, and tab. We want to be + // able to pass this in an HTTP header without encoding. + b >= 32 && b < 127 || b == b'\t' }) { Ok(()) } else { diff --git a/crates/home/Cargo.toml b/crates/home/Cargo.toml index 18459dfef1b..2c5b92bcbb6 100644 --- a/crates/home/Cargo.toml +++ b/crates/home/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "home" version = "0.5.4" # also update `html_root_url` in `src/lib.rs` -authors = [ "Brian Anderson " ] +authors = ["Brian Anderson "] documentation = "https://docs.rs/home" edition = "2018" include = [ @@ -13,8 +13,8 @@ include = [ ] license = "MIT OR Apache-2.0" readme = "README.md" -repository = "https://github.com/brson/home" -description = "Shared definitions of home directories" +repository = "https://github.com/rust-lang/cargo" +description = "Shared definitions of home directories." [target.'cfg(windows)'.dependencies] windows-sys = { version = "0.45.0", features = ["Win32_Foundation", "Win32_UI_Shell"] } diff --git a/crates/home/README.md b/crates/home/README.md index db2ba9259a4..a80adbd3b35 100644 --- a/crates/home/README.md +++ b/crates/home/README.md @@ -1,5 +1,5 @@ [![Documentation](https://docs.rs/home/badge.svg)](https://docs.rs/home) -[![Crates.io](https://img.shields.io/crates/v/home.svg)](https://crates.io/crates/home) +[![crates.io](https://img.shields.io/crates/v/home.svg)](https://crates.io/crates/home) Canonical definitions of `home_dir`, `cargo_home`, and `rustup_home`. @@ -12,7 +12,7 @@ incorrect because it considers the `HOME` environment variable on Windows. This causes surprising situations where a Rust program will behave differently depending on whether it is run under a Unix emulation environment like Cygwin or MinGW. Neither Cargo nor rustup -use the standard libraries definition - they use the definition here. +use the standard library's definition - they use the definition here. This crate further provides two functions, `cargo_home` and `rustup_home`, which are the canonical way to determine the location diff --git a/crates/resolver-tests/Cargo.toml b/crates/resolver-tests/Cargo.toml index cc50ad3b43a..e4aab4325f7 100644 --- a/crates/resolver-tests/Cargo.toml +++ b/crates/resolver-tests/Cargo.toml @@ -8,5 +8,5 @@ cargo = { path = "../.." } cargo-util = { path = "../cargo-util" } is-terminal = "0.4.0" lazy_static = "1.3.0" -proptest = "0.9.1" +proptest = "1.1.0" varisat = "0.2.1" diff --git a/crates/resolver-tests/tests/resolve.rs b/crates/resolver-tests/tests/resolve.rs index 9d8f25a2bd8..df74826f078 100644 --- a/crates/resolver-tests/tests/resolve.rs +++ b/crates/resolver-tests/tests/resolve.rs @@ -100,6 +100,64 @@ proptest! { } } + /// NOTE: if you think this test has failed spuriously see the note at the top of this macro. + #[test] + fn prop_direct_minimum_version_error_implications( + PrettyPrintRegistry(input) in registry_strategy(50, 20, 60) + ) { + let mut config = Config::default().unwrap(); + config.nightly_features_allowed = true; + config + .configure( + 1, + false, + None, + false, + false, + false, + &None, + &["direct-minimal-versions".to_string()], + &[], + ) + .unwrap(); + + let reg = registry(input.clone()); + // there is only a small chance that any one + // crate will be interesting. + // So we try some of the most complicated. + for this in input.iter().rev().take(10) { + // direct-minimal-versions reduces the number of available solutions, so we verify that + // we do not come up with solutions maximal versions does not + let res = resolve( + vec![dep_req(&this.name(), &format!("={}", this.version()))], + ®, + ); + + let mres = resolve_with_config( + vec![dep_req(&this.name(), &format!("={}", this.version()))], + ®, + &config, + ); + + if res.is_err() { + prop_assert!( + mres.is_err(), + "direct-minimal-versions should not have more solutions than the regular, maximal resolver but found one when resolving `{} = \"={}\"`", + this.name(), + this.version() + ) + } + if mres.is_ok() { + prop_assert!( + res.is_ok(), + "direct-minimal-versions should not have more solutions than the regular, maximal resolver but found one when resolving `{} = \"={}\"`", + this.name(), + this.version() + ) + } + } + } + /// NOTE: if you think this test has failed spuriously see the note at the top of this macro. #[test] fn prop_removing_a_dep_cant_break( diff --git a/deny.toml b/deny.toml new file mode 100644 index 00000000000..89d08eacc8f --- /dev/null +++ b/deny.toml @@ -0,0 +1,273 @@ +# This template contains all of the possible sections and their default values + +# Note that all fields that take a lint level have these possible values: +# * deny - An error will be produced and the check will fail +# * warn - A warning will be produced, but the check will not fail +# * allow - No warning or error will be produced, though in some cases a note +# will be + +# The values provided in this template are the default values that will be used +# when any section or field is not specified in your own configuration + +# Root options + +# If 1 or more target triples (and optionally, target_features) are specified, +# only the specified targets will be checked when running `cargo deny check`. +# This means, if a particular package is only ever used as a target specific +# dependency, such as, for example, the `nix` crate only being used via the +# `target_family = "unix"` configuration, that only having windows targets in +# this list would mean the nix crate, as well as any of its exclusive +# dependencies not shared by any other crates, would be ignored, as the target +# list here is effectively saying which targets you are building for. +targets = [ + # The triple can be any string, but only the target triples built in to + # rustc (as of 1.40) can be checked against actual config expressions + #{ triple = "x86_64-unknown-linux-musl" }, + # You can also specify which target_features you promise are enabled for a + # particular target. target_features are currently not validated against + # the actual valid features supported by the target architecture. + #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, +] +# When creating the dependency graph used as the source of truth when checks are +# executed, this field can be used to prune crates from the graph, removing them +# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate +# is pruned from the graph, all of its dependencies will also be pruned unless +# they are connected to another crate in the graph that hasn't been pruned, +# so it should be used with care. The identifiers are [Package ID Specifications] +# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html) +#exclude = [] +# If true, metadata will be collected with `--all-features`. Note that this can't +# be toggled off if true, if you want to conditionally enable `--all-features` it +# is recommended to pass `--all-features` on the cmd line instead +all-features = false +# If true, metadata will be collected with `--no-default-features`. The same +# caveat with `all-features` applies +no-default-features = false +# If set, these feature will be enabled when collecting metadata. If `--features` +# is specified on the cmd line they will take precedence over this option. +#features = [] +# When outputting inclusion graphs in diagnostics that include features, this +# option can be used to specify the depth at which feature edges will be added. +# This option is included since the graphs can be quite large and the addition +# of features from the crate(s) to all of the graph roots can be far too verbose. +# This option can be overridden via `--feature-depth` on the cmd line +feature-depth = 1 + +# This section is considered when running `cargo deny check advisories` +# More documentation for the advisories section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html +[advisories] +# The path where the advisory database is cloned/fetched into +db-path = "~/.cargo/advisory-db" +# The url(s) of the advisory databases to use +db-urls = ["https://github.com/rustsec/advisory-db"] +# The lint level for security vulnerabilities +vulnerability = "deny" +# The lint level for unmaintained crates +unmaintained = "warn" +# The lint level for crates that have been yanked from their source registry +yanked = "warn" +# The lint level for crates with security notices. Note that as of +# 2019-12-17 there are no security notice advisories in +# https://github.com/rustsec/advisory-db +notice = "warn" +# A list of advisory IDs to ignore. Note that ignored advisories will still +# output a note when they are encountered. +ignore = [ + #"RUSTSEC-0000-0000", +] +# Threshold for security vulnerabilities, any vulnerability with a CVSS score +# lower than the range specified will be ignored. Note that ignored advisories +# will still output a note when they are encountered. +# * None - CVSS Score 0.0 +# * Low - CVSS Score 0.1 - 3.9 +# * Medium - CVSS Score 4.0 - 6.9 +# * High - CVSS Score 7.0 - 8.9 +# * Critical - CVSS Score 9.0 - 10.0 +#severity-threshold = + +# If this is true, then cargo deny will use the git executable to fetch advisory database. +# If this is false, then it uses a built-in git library. +# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. +# See Git Authentication for more information about setting up git authentication. +#git-fetch-with-cli = true + +# This section is considered when running `cargo deny check licenses` +# More documentation for the licenses section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html +[licenses] +# The lint level for crates which do not have a detectable license +unlicensed = "deny" +# List of explicitly allowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +allow = [ + "MIT", + "MIT-0", + "Apache-2.0", + "BSD-3-Clause", + "MPL-2.0", + "Unicode-DFS-2016", + "CC0-1.0", +] +# List of explicitly disallowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +deny = [ + #"Nokia", +] +# Lint level for licenses considered copyleft +copyleft = "warn" +# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses +# * both - The license will be approved if it is both OSI-approved *AND* FSF +# * either - The license will be approved if it is either OSI-approved *OR* FSF +# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF +# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved +# * neither - This predicate is ignored and the default lint level is used +allow-osi-fsf-free = "neither" +# Lint level used when no other predicates are matched +# 1. License isn't in the allow or deny lists +# 2. License isn't copyleft +# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither" +default = "deny" +# The confidence threshold for detecting a license from license text. +# The higher the value, the more closely the license text must be to the +# canonical license text of a valid SPDX license file. +# [possible values: any between 0.0 and 1.0]. +confidence-threshold = 0.8 +# Allow 1 or more licenses on a per-crate basis, so that particular licenses +# aren't accepted for every possible crate as with the normal allow list +exceptions = [ + # Each entry is the crate and version constraint, and its specific allow + # list + #{ allow = ["Zlib"], name = "adler32", version = "*" }, +] + +# Some crates don't have (easily) machine readable licensing information, +# adding a clarification entry for it allows you to manually specify the +# licensing information +#[[licenses.clarify]] +# The name of the crate the clarification applies to +#name = "ring" +# The optional version constraint for the crate +#version = "*" +# The SPDX expression for the license requirements of the crate +#expression = "MIT AND ISC AND OpenSSL" +# One or more files in the crate's source used as the "source of truth" for +# the license expression. If the contents match, the clarification will be used +# when running the license check, otherwise the clarification will be ignored +# and the crate will be checked normally, which may produce warnings or errors +# depending on the rest of your configuration +#license-files = [ + # Each entry is a crate relative path, and the (opaque) hash of its contents + #{ path = "LICENSE", hash = 0xbd0eed23 } +#] + +[licenses.private] +# If true, ignores workspace crates that aren't published, or are only +# published to private registries. +# To see how to mark a crate as unpublished (to the official registry), +# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. +ignore = false +# One or more private registries that you might publish crates to, if a crate +# is only published to private registries, and ignore is true, the crate will +# not have its license(s) checked +registries = [ + #"https://sekretz.com/registry +] + +# This section is considered when running `cargo deny check bans`. +# More documentation about the 'bans' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html +[bans] +# Lint level for when multiple versions of the same crate are detected +multiple-versions = "warn" +# Lint level for when a crate version requirement is `*` +wildcards = "allow" +# The graph highlighting used when creating dotgraphs for crates +# with multiple versions +# * lowest-version - The path to the lowest versioned duplicate is highlighted +# * simplest-path - The path to the version with the fewest edges is highlighted +# * all - Both lowest-version and simplest-path are used +highlight = "all" +# The default lint level for `default` features for crates that are members of +# the workspace that is being checked. This can be overriden by allowing/denying +# `default` on a crate-by-crate basis if desired. +workspace-default-features = "allow" +# The default lint level for `default` features for external crates that are not +# members of the workspace. This can be overriden by allowing/denying `default` +# on a crate-by-crate basis if desired. +external-default-features = "allow" +# List of crates that are allowed. Use with care! +allow = [ + #{ name = "ansi_term", version = "=0.11.0" }, +] +# List of crates to deny +deny = [ + # Each entry the name of a crate and a version range. If version is + # not specified, all versions will be matched. + #{ name = "ansi_term", version = "=0.11.0" }, + # + # Wrapper crates can optionally be specified to allow the crate when it + # is a direct dependency of the otherwise banned crate + #{ name = "ansi_term", version = "=0.11.0", wrappers = [] }, +] + +# List of features to allow/deny +# Each entry the name of a crate and a version range. If version is +# not specified, all versions will be matched. +#[[bans.features]] +#name = "reqwest" +# Features to not allow +#deny = ["json"] +# Features to allow +#allow = [ +# "rustls", +# "__rustls", +# "__tls", +# "hyper-rustls", +# "rustls", +# "rustls-pemfile", +# "rustls-tls-webpki-roots", +# "tokio-rustls", +# "webpki-roots", +#] +# If true, the allowed features must exactly match the enabled feature set. If +# this is set there is no point setting `deny` +#exact = true + +# Certain crates/versions that will be skipped when doing duplicate detection. +skip = [ + #{ name = "ansi_term", version = "=0.11.0" }, +] +# Similarly to `skip` allows you to skip certain crates during duplicate +# detection. Unlike skip, it also includes the entire tree of transitive +# dependencies starting at the specified crate, up to a certain depth, which is +# by default infinite. +skip-tree = [ + #{ name = "ansi_term", version = "=0.11.0", depth = 20 }, +] + +# This section is considered when running `cargo deny check sources`. +# More documentation about the 'sources' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html +[sources] +# Lint level for what to happen when a crate from a crate registry that is not +# in the allow list is encountered +unknown-registry = "warn" +# Lint level for what to happen when a crate from a git repository that is not +# in the allow list is encountered +unknown-git = "warn" +# List of URLs for allowed crate registries. Defaults to the crates.io index +# if not specified. If it is specified but empty, no registries are allowed. +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +# List of URLs for allowed Git repositories +allow-git = [] + +[sources.allow-org] +# 1 or more github.com organizations to allow git sources for +github = [] +# 1 or more gitlab.com organizations to allow git sources for +gitlab = [] +# 1 or more bitbucket.org organizations to allow git sources for +bitbucket = [] diff --git a/src/bin/cargo/cli.rs b/src/bin/cargo/cli.rs index 99b0ccef21e..17120a656ce 100644 --- a/src/bin/cargo/cli.rs +++ b/src/bin/cargo/cli.rs @@ -31,6 +31,23 @@ pub fn main(config: &mut LazyConfig) -> CliResult { // This must be completed before config is initialized assert_eq!(config.is_init(), false); if let Some(new_cwd) = args.get_one::("directory") { + // This is a temporary hack. This cannot access `Config`, so this is a bit messy. + // This does not properly parse `-Z` flags that appear after the subcommand. + // The error message is not as helpful as the standard one. + let nightly_features_allowed = matches!(&*features::channel(), "nightly" | "dev"); + if !nightly_features_allowed + || (nightly_features_allowed + && !args + .get_many("unstable-features") + .map(|mut z| z.any(|value: &String| value == "unstable-options")) + .unwrap_or(false)) + { + return Err(anyhow::format_err!( + "the `-C` flag is unstable, \ + pass `-Z unstable-options` on the nightly channel to enable it" + ) + .into()); + } std::env::set_current_dir(&new_cwd).context("could not change to requested directory")?; } @@ -413,6 +430,9 @@ impl GlobalArgs { } pub fn cli() -> Command { + // ALLOWED: `RUSTUP_HOME` should only be read from process env, otherwise + // other tools may point to executables from incompatible distributions. + #[allow(clippy::disallowed_methods)] let is_rustup = std::env::var_os("RUSTUP_HOME").is_some(); let usage = if is_rustup { "cargo [+toolchain] [OPTIONS] [COMMAND]" @@ -476,7 +496,7 @@ See 'cargo help ' for more information on a specific command.\n", ) .arg( Arg::new("directory") - .help("Change to DIRECTORY before doing anything") + .help("Change to DIRECTORY before doing anything (nightly-only)") .short('C') .value_name("DIRECTORY") .value_hint(clap::ValueHint::DirPath) diff --git a/src/bin/cargo/commands/install.rs b/src/bin/cargo/commands/install.rs index 790bfd2d7bc..8197a16900f 100644 --- a/src/bin/cargo/commands/install.rs +++ b/src/bin/cargo/commands/install.rs @@ -80,6 +80,7 @@ pub fn cli() -> Command { .requires("crate") .conflicts_with_all(&["git", "path", "index"]), ) + .arg_ignore_rust_version() .arg_message_format() .arg_timings() .after_help("Run `cargo help install` for more detailed information.\n") diff --git a/src/bin/cargo/commands/login.rs b/src/bin/cargo/commands/login.rs index dac0457d9e0..1c8d3ae4cff 100644 --- a/src/bin/cargo/commands/login.rs +++ b/src/bin/cargo/commands/login.rs @@ -34,10 +34,11 @@ pub fn cli() -> Command { } pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult { + let registry = args.registry(config)?; ops::registry_login( config, args.get_one::("token").map(|s| s.as_str().into()), - args.get_one("registry").map(String::as_str), + registry.as_deref(), args.flag("generate-keypair"), args.flag("secret-key"), args.get_one("key-subject").map(String::as_str), diff --git a/src/bin/cargo/commands/logout.rs b/src/bin/cargo/commands/logout.rs index bc16ee55ecf..0b4d8b83f4b 100644 --- a/src/bin/cargo/commands/logout.rs +++ b/src/bin/cargo/commands/logout.rs @@ -10,14 +10,7 @@ pub fn cli() -> Command { } pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult { - if !config.cli_unstable().credential_process { - config - .cli_unstable() - .fail_if_stable_command(config, "logout", 8933)?; - } - ops::registry_logout( - config, - args.get_one::("registry").map(String::as_str), - )?; + let registry = args.registry(config)?; + ops::registry_logout(config, registry.as_deref())?; Ok(()) } diff --git a/src/bin/cargo/commands/tree.rs b/src/bin/cargo/commands/tree.rs index 0a75178f589..94bf3fff106 100644 --- a/src/bin/cargo/commands/tree.rs +++ b/src/bin/cargo/commands/tree.rs @@ -79,7 +79,7 @@ pub fn cli() -> Command { .alias("duplicate"), ) .arg( - opt("charset", "Character set to use in output: utf8, ascii") + opt("charset", "Character set to use in output") .value_name("CHARSET") .value_parser(["utf8", "ascii"]) .default_value("utf8"), diff --git a/src/bin/cargo/main.rs b/src/bin/cargo/main.rs index 28cd18d248b..55da2997fdb 100644 --- a/src/bin/cargo/main.rs +++ b/src/bin/cargo/main.rs @@ -1,5 +1,6 @@ #![warn(rust_2018_idioms)] // while we're getting used to 2018 #![allow(clippy::all)] +#![warn(clippy::disallowed_methods)] use cargo::util::toml::StringOrVec; use cargo::util::CliError; diff --git a/src/cargo/core/compiler/compilation.rs b/src/cargo/core/compiler/compilation.rs index ffd3881dc57..29642f13d79 100644 --- a/src/cargo/core/compiler/compilation.rs +++ b/src/cargo/core/compiler/compilation.rs @@ -343,6 +343,10 @@ impl<'cfg> Compilation<'cfg> { "CARGO_PKG_RUST_VERSION", &pkg.rust_version().unwrap_or(&String::new()), ) + .env( + "CARGO_PKG_README", + metadata.readme.as_ref().unwrap_or(&String::new()), + ) .cwd(pkg.root()); // Apply any environment variables from the config diff --git a/src/cargo/core/compiler/compile_kind.rs b/src/cargo/core/compiler/compile_kind.rs index 45a464684a6..73d8f89cc44 100644 --- a/src/cargo/core/compiler/compile_kind.rs +++ b/src/cargo/core/compiler/compile_kind.rs @@ -3,7 +3,7 @@ use crate::core::Target; use crate::util::errors::CargoResult; use crate::util::interning::InternedString; -use crate::util::{Config, StableHasher}; +use crate::util::{try_canonicalize, Config, StableHasher}; use anyhow::Context as _; use serde::Serialize; use std::collections::BTreeSet; @@ -138,8 +138,7 @@ impl CompileTarget { // If `name` ends in `.json` then it's likely a custom target // specification. Canonicalize the path to ensure that different builds // with different paths always produce the same result. - let path = Path::new(name) - .canonicalize() + let path = try_canonicalize(Path::new(name)) .with_context(|| format!("target path {:?} is not a valid file", name))?; let name = path diff --git a/src/cargo/core/compiler/context/mod.rs b/src/cargo/core/compiler/context/mod.rs index 0d87d89d810..3f13f086c95 100644 --- a/src/cargo/core/compiler/context/mod.rs +++ b/src/cargo/core/compiler/context/mod.rs @@ -71,11 +71,6 @@ pub struct Context<'a, 'cfg> { /// metadata files in addition to the rlib itself. rmeta_required: HashSet, - /// When we're in jobserver-per-rustc process mode, this keeps those - /// jobserver clients for each Unit (which eventually becomes a rustc - /// process). - pub rustc_clients: HashMap, - /// Map of the LTO-status of each unit. This indicates what sort of /// compilation is happening (only object, only bitcode, both, etc), and is /// precalculated early on. @@ -124,7 +119,6 @@ impl<'a, 'cfg> Context<'a, 'cfg> { primary_packages: HashSet::new(), files: None, rmeta_required: HashSet::new(), - rustc_clients: HashMap::new(), lto: HashMap::new(), metadata_for_doc_units: HashMap::new(), failed_scrape_units: Arc::new(Mutex::new(HashSet::new())), @@ -614,24 +608,6 @@ impl<'a, 'cfg> Context<'a, 'cfg> { self.rmeta_required.contains(unit) } - /// Used by `-Zjobserver-per-rustc`. - pub fn new_jobserver(&mut self) -> CargoResult { - let tokens = self.bcx.jobs() as usize; - let client = Client::new(tokens).with_context(|| "failed to create jobserver")?; - - // Drain the client fully - for i in 0..tokens { - client.acquire_raw().with_context(|| { - format!( - "failed to fully drain {}/{} token from jobserver at startup", - i, tokens, - ) - })?; - } - - Ok(client) - } - /// Finds metadata for Doc/Docscrape units. /// /// rustdoc needs a -Cmetadata flag in order to recognize StableCrateIds that refer to diff --git a/src/cargo/core/compiler/custom_build.rs b/src/cargo/core/compiler/custom_build.rs index 40df22bb7ae..5728d0c8573 100644 --- a/src/cargo/core/compiler/custom_build.rs +++ b/src/cargo/core/compiler/custom_build.rs @@ -231,14 +231,11 @@ fn build_work(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult { for cfg in bcx.target_data.cfg(unit.kind) { match *cfg { Cfg::Name(ref n) => { - cfg_map.insert(n.clone(), None); + cfg_map.insert(n.clone(), Vec::new()); } Cfg::KeyPair(ref k, ref v) => { - if let Some(ref mut values) = - *cfg_map.entry(k.clone()).or_insert_with(|| Some(Vec::new())) - { - values.push(v.clone()) - } + let values = cfg_map.entry(k.clone()).or_default(); + values.push(v.clone()); } } } @@ -249,14 +246,7 @@ fn build_work(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult { continue; } let k = format!("CARGO_CFG_{}", super::envify(&k)); - match v { - Some(list) => { - cmd.env(&k, list.join(",")); - } - None => { - cmd.env(&k, ""); - } - } + cmd.env(&k, v.join(",")); } // Also inform the build script of the rustc compiler context. @@ -420,6 +410,10 @@ fn build_work(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult { // If we're opting into backtraces, mention that build dependencies' backtraces can // be improved by requesting debuginfo to be built, if we're not building with // debuginfo already. + // + // ALLOWED: Other tools like `rustc` might read it directly + // through `std::env`. We should make their behavior consistent. + #[allow(clippy::disallowed_methods)] if let Ok(show_backtraces) = std::env::var("RUST_BACKTRACE") { if !built_with_debuginfo && show_backtraces != "0" { build_error_context.push_str(&format!( @@ -737,6 +731,10 @@ impl BuildOutput { None => return false, Some(n) => n, }; + // ALLOWED: the process of rustc boostrapping reads this through + // `std::env`. We should make the behavior consistent. Also, we + // don't advertise this for bypassing nightly. + #[allow(clippy::disallowed_methods)] std::env::var("RUSTC_BOOTSTRAP") .map_or(false, |var| var.split(',').any(|s| s == name)) }; diff --git a/src/cargo/core/compiler/fingerprint/mod.rs b/src/cargo/core/compiler/fingerprint/mod.rs index e47ae07f844..7401afebca4 100644 --- a/src/cargo/core/compiler/fingerprint/mod.rs +++ b/src/cargo/core/compiler/fingerprint/mod.rs @@ -367,9 +367,9 @@ use serde::{Deserialize, Serialize}; use crate::core::compiler::unit_graph::UnitDep; use crate::core::Package; -use crate::util; use crate::util::errors::CargoResult; use crate::util::interning::InternedString; +use crate::util::{self, try_canonicalize}; use crate::util::{internal, path_args, profile, StableHasher}; use crate::{Config, CARGO_ENV}; @@ -764,6 +764,19 @@ pub enum StaleItem { } impl LocalFingerprint { + /// Read the environment variable of the given env `key`, and creates a new + /// [`LocalFingerprint::RerunIfEnvChanged`] for it. + /// + // TODO: This is allowed at this moment. Should figure out if it makes + // sense if permitting to read env from the config system. + #[allow(clippy::disallowed_methods)] + fn from_env>(key: K) -> LocalFingerprint { + let key = key.as_ref(); + let var = key.to_owned(); + let val = env::var(key).ok(); + LocalFingerprint::RerunIfEnvChanged { var, val } + } + /// Checks dynamically at runtime if this `LocalFingerprint` has a stale /// item inside of it. /// @@ -1661,10 +1674,7 @@ fn local_fingerprints_deps( local.extend( deps.rerun_if_env_changed .iter() - .map(|var| LocalFingerprint::RerunIfEnvChanged { - var: var.clone(), - val: env::var(var).ok(), - }), + .map(LocalFingerprint::from_env), ); local @@ -1941,8 +1951,8 @@ pub fn translate_dep_info( ) -> CargoResult<()> { let depinfo = parse_rustc_dep_info(rustc_dep_info)?; - let target_root = target_root.canonicalize()?; - let pkg_root = pkg_root.canonicalize()?; + let target_root = try_canonicalize(target_root)?; + let pkg_root = try_canonicalize(pkg_root)?; let mut on_disk_info = EncodedDepInfo::default(); on_disk_info.env = depinfo.env; @@ -1985,7 +1995,7 @@ pub fn translate_dep_info( // If canonicalization fails, just use the abs path. There is currently // a bug where --remap-path-prefix is affecting .d files, causing them // to point to non-existent paths. - let canon_file = abs_file.canonicalize().unwrap_or_else(|_| abs_file.clone()); + let canon_file = try_canonicalize(&abs_file).unwrap_or_else(|_| abs_file.clone()); let (ty, path) = if let Ok(stripped) = canon_file.strip_prefix(&target_root) { (DepInfoPathType::TargetRootRelative, stripped) diff --git a/src/cargo/core/compiler/job_queue/job_state.rs b/src/cargo/core/compiler/job_queue/job_state.rs index 9bd376aace9..a513d3b8986 100644 --- a/src/cargo/core/compiler/job_queue/job_state.rs +++ b/src/cargo/core/compiler/job_queue/job_state.rs @@ -194,20 +194,4 @@ impl<'a, 'cfg> JobState<'a, 'cfg> { self.messages .push(Message::FutureIncompatReport(self.id, report)); } - - /// The rustc underlying this Job is about to acquire a jobserver token (i.e., block) - /// on the passed client. - /// - /// This should arrange for the associated client to eventually get a token via - /// `client.release_raw()`. - pub fn will_acquire(&self) { - self.messages.push(Message::NeedsToken(self.id)); - } - - /// The rustc underlying this Job is informing us that it is done with a jobserver token. - /// - /// Note that it does *not* write that token back anywhere. - pub fn release_token(&self) { - self.messages.push(Message::ReleaseToken(self.id)); - } } diff --git a/src/cargo/core/compiler/job_queue/mod.rs b/src/cargo/core/compiler/job_queue/mod.rs index 2c055e42eb5..38ab0fe49a2 100644 --- a/src/cargo/core/compiler/job_queue/mod.rs +++ b/src/cargo/core/compiler/job_queue/mod.rs @@ -30,46 +30,30 @@ //! //! ## Jobserver //! -//! Cargo and rustc have a somewhat non-trivial jobserver relationship with each -//! other, which is due to scaling issues with sharing a single jobserver -//! amongst what is potentially hundreds of threads of work on many-cored -//! systems on (at least) Linux, and likely other platforms as well. +//! As of Feb. 2023, Cargo and rustc have a relatively simple jobserver +//! relationship with each other. They share a single jobserver amongst what +//! is potentially hundreds of threads of work on many-cored systems. +//! The jobserver could come from either the environment (e.g., from a `make` +//! invocation), or from Cargo creating its own jobserver server if there is no +//! jobserver to inherit from. //! //! Cargo wants to complete the build as quickly as possible, fully saturating -//! all cores (as constrained by the -j=N) parameter. Cargo also must not spawn +//! all cores (as constrained by the `-j=N`) parameter. Cargo also must not spawn //! more than N threads of work: the total amount of tokens we have floating //! around must always be limited to N. //! -//! It is not really possible to optimally choose which crate should build first -//! or last; nor is it possible to decide whether to give an additional token to -//! rustc first or rather spawn a new crate of work. For now, the algorithm we -//! implement prioritizes spawning as many crates (i.e., rustc processes) as -//! possible, and then filling each rustc with tokens on demand. +//! It is not really possible to optimally choose which crate should build +//! first or last; nor is it possible to decide whether to give an additional +//! token to rustc first or rather spawn a new crate of work. The algorithm in +//! Cargo prioritizes spawning as many crates (i.e., rustc processes) as +//! possible. In short, the jobserver relationship among Cargo and rustc +//! processes is **1 `cargo` to N `rustc`**. Cargo knows nothing beyond rustc +//! processes in terms of parallelism[^parallel-rustc]. //! -//! We integrate with the [jobserver], originating from GNU make, to make sure -//! that build scripts which use make to build C code can cooperate with us on -//! the number of used tokens and avoid overfilling the system we're on. -//! -//! The jobserver is unfortunately a very simple protocol, so we enhance it a -//! little when we know that there is a rustc on the other end. Via the stderr -//! pipe we have to rustc, we get messages such as `NeedsToken` and -//! `ReleaseToken` from rustc. -//! -//! [`NeedsToken`] indicates that a rustc is interested in acquiring a token, -//! but never that it would be impossible to make progress without one (i.e., -//! it would be incorrect for rustc to not terminate due to an unfulfilled -//! `NeedsToken` request); we do not usually fulfill all `NeedsToken` requests for a -//! given rustc. -//! -//! [`ReleaseToken`] indicates that a rustc is done with one of its tokens and -//! is ready for us to re-acquire ownership — we will either release that token -//! back into the general pool or reuse it ourselves. Note that rustc will -//! inform us that it is releasing a token even if it itself is also requesting -//! tokens; is up to us whether to return the token to that same rustc. -//! -//! `jobserver` also manages the allocation of tokens to rustc beyond -//! the implicit token each rustc owns (i.e., the ones used for parallel LLVM -//! work and parallel rustc threads). +//! We integrate with the [jobserver] crate, originating from GNU make +//! [POSIX jobserver], to make sure that build scripts which use make to +//! build C code can cooperate with us on the number of used tokens and +//! avoid overfilling the system we're on. //! //! ## Scheduling //! @@ -113,9 +97,16 @@ //! //! See [`Message`] for all available message kinds. //! +//! [^parallel-rustc]: In fact, `jobserver` that Cargo uses also manages the +//! allocation of tokens to rustc beyond the implicit token each rustc owns +//! (i.e., the ones used for parallel LLVM work and parallel rustc threads). +//! See also ["Rust Compiler Development Guide: Parallel Compilation"] +//! and [this comment][rustc-codegen] in rust-lang/rust. +//! +//! ["Rust Compiler Development Guide: Parallel Compilation"]: https://rustc-dev-guide.rust-lang.org/parallel-rustc.html +//! [rustc-codegen]: https://github.com/rust-lang/rust/blob/5423745db8b434fcde54888b35f518f00cce00e4/compiler/rustc_codegen_ssa/src/back/write.rs#L1204-L1217 //! [jobserver]: https://docs.rs/jobserver -//! [`NeedsToken`]: Message::NeedsToken -//! [`ReleaseToken`]: Message::ReleaseToken +//! [POSIX jobserver]: https://www.gnu.org/software/make/manual/html_node/POSIX-Jobserver.html //! [`push`]: Queue::push //! [`push_bounded`]: Queue::push_bounded @@ -123,7 +114,7 @@ mod job; mod job_state; use std::cell::RefCell; -use std::collections::{BTreeMap, HashMap, HashSet}; +use std::collections::{HashMap, HashSet}; use std::fmt::Write as _; use std::io; use std::path::{Path, PathBuf}; @@ -133,7 +124,7 @@ use std::time::Duration; use anyhow::{format_err, Context as _}; use cargo_util::ProcessBuilder; -use jobserver::{Acquired, Client, HelperThread}; +use jobserver::{Acquired, HelperThread}; use log::{debug, trace}; use semver::Version; @@ -143,6 +134,7 @@ pub use self::job_state::JobState; use super::context::OutputFile; use super::timings::Timings; use super::{BuildContext, BuildPlan, CompileMode, Context, Unit}; +use crate::core::compiler::descriptive_pkg_name; use crate::core::compiler::future_incompat::{ self, FutureBreakageItem, FutureIncompatReportPackage, }; @@ -199,13 +191,6 @@ struct DrainState<'cfg> { /// single rustc process. tokens: Vec, - /// rustc per-thread tokens, when in jobserver-per-rustc mode. - rustc_tokens: HashMap>, - - /// This represents the list of rustc jobs (processes) and associated - /// clients that are interested in receiving a token. - to_send_clients: BTreeMap>, - /// The list of jobs that we have not yet started executing, but have /// retrieved from the `queue`. We eagerly pull jobs off the main queue to /// allow us to request jobserver tokens pretty early. @@ -387,12 +372,6 @@ enum Message { Token(io::Result), Finish(JobId, Artifact, CargoResult<()>), FutureIncompatReport(JobId, Vec), - - // This client should get release_raw called on it with one of our tokens - NeedsToken(JobId), - - // A token previously passed to a NeedsToken client is being released. - ReleaseToken(JobId), } impl<'cfg> JobQueue<'cfg> { @@ -507,8 +486,6 @@ impl<'cfg> JobQueue<'cfg> { next_id: 0, timings: self.timings, tokens: Vec::new(), - rustc_tokens: HashMap::new(), - to_send_clients: BTreeMap::new(), pending_queue: Vec::new(), print: DiagnosticPrinter::new(cx.bcx.config), finished: 0, @@ -600,46 +577,9 @@ impl<'cfg> DrainState<'cfg> { self.active.len() < self.tokens.len() + 1 } - // The oldest job (i.e., least job ID) is the one we grant tokens to first. - fn pop_waiting_client(&mut self) -> (JobId, Client) { - // FIXME: replace this with BTreeMap::first_entry when that stabilizes. - let key = *self - .to_send_clients - .keys() - .next() - .expect("at least one waiter"); - let clients = self.to_send_clients.get_mut(&key).unwrap(); - let client = clients.pop().unwrap(); - if clients.is_empty() { - self.to_send_clients.remove(&key); - } - (key, client) - } - - // If we managed to acquire some extra tokens, send them off to a waiting rustc. - fn grant_rustc_token_requests(&mut self) -> CargoResult<()> { - while !self.to_send_clients.is_empty() && self.has_extra_tokens() { - let (id, client) = self.pop_waiting_client(); - // This unwrap is guaranteed to succeed. `active` must be at least - // length 1, as otherwise there can't be a client waiting to be sent - // on, so tokens.len() must also be at least one. - let token = self.tokens.pop().unwrap(); - self.rustc_tokens - .entry(id) - .or_insert_with(Vec::new) - .push(token); - client - .release_raw() - .with_context(|| "failed to release jobserver token")?; - } - - Ok(()) - } - fn handle_event( &mut self, cx: &mut Context<'_, '_>, - jobserver_helper: &HelperThread, plan: &mut BuildPlan, event: Message, ) -> Result<(), ErrorToHandle> { @@ -699,19 +639,6 @@ impl<'cfg> DrainState<'cfg> { Artifact::All => { trace!("end: {:?}", id); self.finished += 1; - if let Some(rustc_tokens) = self.rustc_tokens.remove(&id) { - // This puts back the tokens that this rustc - // acquired into our primary token list. - // - // This represents a rustc bug: it did not - // release all of its thread tokens but finished - // completely. But we want to make Cargo resilient - // to such rustc bugs, as they're generally not - // fatal in nature (i.e., Cargo can make progress - // still, and the build might not even fail). - self.tokens.extend(rustc_tokens); - } - self.to_send_clients.remove(&id); self.report_warning_count( cx.bcx.config, id, @@ -756,31 +683,6 @@ impl<'cfg> DrainState<'cfg> { let token = acquired_token.with_context(|| "failed to acquire jobserver token")?; self.tokens.push(token); } - Message::NeedsToken(id) => { - trace!("queue token request"); - jobserver_helper.request_token(); - let client = cx.rustc_clients[&self.active[&id]].clone(); - self.to_send_clients - .entry(id) - .or_insert_with(Vec::new) - .push(client); - } - Message::ReleaseToken(id) => { - // Note that this pops off potentially a completely - // different token, but all tokens of the same job are - // conceptually the same so that's fine. - // - // self.tokens is a "pool" -- the order doesn't matter -- and - // this transfers ownership of the token into that pool. If we - // end up using it on the next go around, then this token will - // be truncated, same as tokens obtained through Message::Token. - let rustc_tokens = self - .rustc_tokens - .get_mut(&id) - .expect("no tokens associated"); - self.tokens - .push(rustc_tokens.pop().expect("rustc releases token it has")); - } } Ok(()) @@ -795,19 +697,6 @@ impl<'cfg> DrainState<'cfg> { // listen for a message with a timeout, and on timeout we run the // previous parts of the loop again. let mut events = self.messages.try_pop_all(); - trace!( - "tokens in use: {}, rustc_tokens: {:?}, waiting_rustcs: {:?} (events this tick: {})", - self.tokens.len(), - self.rustc_tokens - .iter() - .map(|(k, j)| (k, j.len())) - .collect::>(), - self.to_send_clients - .iter() - .map(|(k, j)| (k, j.len())) - .collect::>(), - events.len(), - ); if events.is_empty() { loop { self.tick_progress(); @@ -866,17 +755,13 @@ impl<'cfg> DrainState<'cfg> { break; } - if let Err(e) = self.grant_rustc_token_requests() { - self.handle_error(&mut cx.bcx.config.shell(), &mut errors, e); - } - // And finally, before we block waiting for the next event, drop any // excess tokens we may have accidentally acquired. Due to how our // jobserver interface is architected we may acquire a token that we // don't actually use, and if this happens just relinquish it back // to the jobserver itself. for event in self.wait_for_events() { - if let Err(event_err) = self.handle_event(cx, jobserver_helper, plan, event) { + if let Err(event_err) = self.handle_event(cx, plan, event) { self.handle_error(&mut cx.bcx.config.shell(), &mut errors, event_err); } } @@ -970,7 +855,6 @@ impl<'cfg> DrainState<'cfg> { self.active.len(), self.pending_queue.len(), self.queue.len(), - self.rustc_tokens.len(), ); self.timings.record_cpu(); @@ -1117,15 +1001,8 @@ impl<'cfg> DrainState<'cfg> { None | Some(_) => return, }; let unit = &self.active[&id]; - let mut message = format!("`{}` ({}", unit.pkg.name(), unit.target.description_named()); - if unit.mode.is_rustc_test() && !(unit.target.is_test() || unit.target.is_bench()) { - message.push_str(" test"); - } else if unit.mode.is_doc_test() { - message.push_str(" doctest"); - } else if unit.mode.is_doc() { - message.push_str(" doc"); - } - message.push_str(") generated "); + let mut message = descriptive_pkg_name(&unit.pkg.name(), &unit.target, &unit.mode); + message.push_str(" generated "); match count.total { 1 => message.push_str("1 warning"), n => drop(write!(message, "{} warnings", n)), diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs index bcc9a1596f2..7b43fd27d99 100644 --- a/src/cargo/core/compiler/mod.rs +++ b/src/cargo/core/compiler/mod.rs @@ -33,17 +33,17 @@ pub mod artifact; mod build_config; -mod build_context; +pub(crate) mod build_context; mod build_plan; mod compilation; mod compile_kind; -mod context; +pub(crate) mod context; mod crate_type; mod custom_build; -mod fingerprint; +pub(crate) mod fingerprint; pub mod future_incompat; -mod job_queue; -mod layout; +pub(crate) mod job_queue; +pub(crate) mod layout; mod links; mod lto; mod output_depinfo; @@ -467,7 +467,8 @@ fn rustc(cx: &mut Context<'_, '_>, unit: &Unit, exec: &Arc) -> Car 1 => " due to previous error".to_string(), count => format!(" due to {} previous errors", count), }; - format!("could not compile `{}`{}{}", name, errors, warnings) + let name = descriptive_pkg_name(&name, &target, &mode); + format!("could not compile {name}{errors}{warnings}") }); if let Err(e) = result { @@ -715,14 +716,7 @@ fn prepare_rustc( base.env("CARGO_TARGET_TMPDIR", tmp.display().to_string()); } - if cx.bcx.config.cli_unstable().jobserver_per_rustc { - let client = cx.new_jobserver()?; - base.inherit_jobserver(&client); - base.arg("-Z").arg("jobserver-token-requests"); - assert!(cx.rustc_clients.insert(unit.clone(), client).is_none()); - } else { - base.inherit_jobserver(&cx.jobserver); - } + base.inherit_jobserver(&cx.jobserver); build_base_args(cx, &mut base, unit, crate_types)?; build_deps_args(&mut base, cx, unit)?; Ok(base) @@ -1701,31 +1695,6 @@ fn on_stderr_line_inner( return Ok(false); } - #[derive(serde::Deserialize)] - struct JobserverNotification { - jobserver_event: Event, - } - - #[derive(Debug, serde::Deserialize)] - enum Event { - WillAcquire, - Release, - } - - if let Ok(JobserverNotification { jobserver_event }) = - serde_json::from_str::(compiler_message.get()) - { - trace!( - "found jobserver directive from rustc: `{:?}`", - jobserver_event - ); - match jobserver_event { - Event::WillAcquire => state.will_acquire(), - Event::Release => state.release_token(), - } - return Ok(false); - } - // And failing all that above we should have a legitimate JSON diagnostic // from the compiler, so wrap it in an external Cargo JSON message // indicating which package it came from and then emit it. @@ -1808,3 +1777,19 @@ fn replay_output_cache( Ok(()) }) } + +/// Provides a package name with descriptive target information, +/// e.g., '`foo` (bin "bar" test)', '`foo` (lib doctest)'. +fn descriptive_pkg_name(name: &str, target: &Target, mode: &CompileMode) -> String { + let desc_name = target.description_named(); + let mode = if mode.is_rustc_test() && !(target.is_test() || target.is_bench()) { + " test" + } else if mode.is_doc_test() { + " doctest" + } else if mode.is_doc() { + " doc" + } else { + "" + }; + format!("`{name}` ({desc_name}{mode})") +} diff --git a/src/cargo/core/compiler/timings.rs b/src/cargo/core/compiler/timings.rs index c44eebaec09..0e0dc03eeed 100644 --- a/src/cargo/core/compiler/timings.rs +++ b/src/cargo/core/compiler/timings.rs @@ -92,10 +92,6 @@ struct Concurrency { /// Number of units that are not yet ready, because they are waiting for /// dependencies to finish. inactive: usize, - /// Number of rustc "extra" threads -- i.e., how many tokens have been - /// provided across all current rustc instances that are not the main thread - /// tokens. - rustc_parallelism: usize, } impl<'cfg> Timings<'cfg> { @@ -240,13 +236,7 @@ impl<'cfg> Timings<'cfg> { } /// This is called periodically to mark the concurrency of internal structures. - pub fn mark_concurrency( - &mut self, - active: usize, - waiting: usize, - inactive: usize, - rustc_parallelism: usize, - ) { + pub fn mark_concurrency(&mut self, active: usize, waiting: usize, inactive: usize) { if !self.enabled { return; } @@ -255,7 +245,6 @@ impl<'cfg> Timings<'cfg> { active, waiting, inactive, - rustc_parallelism, }; self.concurrency.push(c); } @@ -307,7 +296,7 @@ impl<'cfg> Timings<'cfg> { if !self.enabled { return Ok(()); } - self.mark_concurrency(0, 0, 0, 0); + self.mark_concurrency(0, 0, 0); self.unit_times .sort_unstable_by(|a, b| a.start.partial_cmp(&b.start).unwrap()); if self.report_html { @@ -391,12 +380,6 @@ impl<'cfg> Timings<'cfg> { let num_cpus = available_parallelism() .map(|x| x.get().to_string()) .unwrap_or_else(|_| "n/a".into()); - let max_rustc_concurrency = self - .concurrency - .iter() - .map(|c| c.rustc_parallelism) - .max() - .unwrap(); let rustc_info = render_rustc_info(bcx); let error_msg = match error { Some(e) => format!( @@ -440,9 +423,6 @@ impl<'cfg> Timings<'cfg> { rustc:{} - - Max (global) rustc threads concurrency:{} - {} "#, @@ -457,7 +437,6 @@ impl<'cfg> Timings<'cfg> { self.start_str, total_time, rustc_info, - max_rustc_concurrency, error_msg, )?; Ok(()) diff --git a/src/cargo/core/dependency.rs b/src/cargo/core/dependency.rs index db4fdd07393..0b3aba8ada5 100644 --- a/src/cargo/core/dependency.rs +++ b/src/cargo/core/dependency.rs @@ -106,6 +106,16 @@ pub enum DepKind { Build, } +impl DepKind { + pub fn kind_table(&self) -> &'static str { + match self { + DepKind::Normal => "dependencies", + DepKind::Development => "dev-dependencies", + DepKind::Build => "build-dependencies", + } + } +} + impl ser::Serialize for DepKind { fn serialize(&self, s: S) -> Result where diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index f2c58d9f43f..7f16e79cfa6 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -716,14 +716,15 @@ unstable_cli_options!( doctest_xcompile: bool = ("Compile and run doctests for non-host target using runner config"), dual_proc_macros: bool = ("Build proc-macros for both the host and the target"), features: Option> = (HIDDEN), + gitoxide: Option = ("Use gitoxide for the given git interactions, or all of them if no argument is given"), jobserver_per_rustc: bool = (HIDDEN), minimal_versions: bool = ("Resolve minimal dependency versions instead of maximum"), + direct_minimal_versions: bool = ("Resolve minimal dependency versions instead of maximum (direct dependencies only)"), mtime_on_use: bool = ("Configure Cargo to update the mtime of used files"), no_index_update: bool = ("Do not update the registry index even if the cache is outdated"), panic_abort_tests: bool = ("Enable support to run tests with -Cpanic=abort"), profile_rustflags: bool = ("Enable the `rustflags` option in profiles in .cargo/config.toml file"), host_config: bool = ("Enable the [host] section in the .cargo/config.toml file"), - sparse_registry: bool = ("Use the sparse protocol when accessing crates.io"), registry_auth: bool = ("Authentication for alternative registries, and generate registry authentication tokens using asymmetric cryptography"), target_applies_to_host: bool = ("Enable the `target-applies-to-host` key in the .cargo/config.toml file"), rustdoc_map: bool = ("Allow passing external documentation mappings to rustdoc"), @@ -793,10 +794,7 @@ const STABILISED_MULTITARGET: &str = "Multiple `--target` options are now always const STABILIZED_TERMINAL_WIDTH: &str = "The -Zterminal-width option is now always enabled for terminal output."; -const STABILISED_SPARSE_REGISTRY: &str = "This flag currently still sets the default protocol \ - to `sparse` when accessing crates.io. However, this will be removed in the future. \n\ - The stable equivalent is to set the config value `registries.crates-io.protocol = 'sparse'`\n\ - or environment variable `CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse`"; +const STABILISED_SPARSE_REGISTRY: &str = "The sparse protocol is now the default for crates.io"; fn deserialize_build_std<'de, D>(deserializer: D) -> Result>, D::Error> where @@ -827,6 +825,74 @@ where parse_check_cfg(crates.into_iter()).map_err(D::Error::custom) } +#[derive(Debug, Copy, Clone, Default, Deserialize)] +pub struct GitoxideFeatures { + /// All fetches are done with `gitoxide`, which includes git dependencies as well as the crates index. + pub fetch: bool, + /// When cloning the index, perform a shallow clone. Maintain shallowness upon subsequent fetches. + pub shallow_index: bool, + /// When cloning git dependencies, perform a shallow clone and maintain shallowness on subsequent fetches. + pub shallow_deps: bool, + /// Checkout git dependencies using `gitoxide` (submodules are still handled by git2 ATM, and filters + /// like linefeed conversions are unsupported). + pub checkout: bool, + /// A feature flag which doesn't have any meaning except for preventing + /// `__CARGO_USE_GITOXIDE_INSTEAD_OF_GIT2=1` builds to enable all safe `gitoxide` features. + /// That way, `gitoxide` isn't actually used even though it's enabled. + pub internal_use_git2: bool, +} + +impl GitoxideFeatures { + fn all() -> Self { + GitoxideFeatures { + fetch: true, + shallow_index: true, + checkout: true, + shallow_deps: true, + internal_use_git2: false, + } + } + + /// Features we deem safe for everyday use - typically true when all tests pass with them + /// AND they are backwards compatible. + fn safe() -> Self { + GitoxideFeatures { + fetch: true, + shallow_index: false, + checkout: true, + shallow_deps: false, + internal_use_git2: false, + } + } +} + +fn parse_gitoxide( + it: impl Iterator>, +) -> CargoResult> { + let mut out = GitoxideFeatures::default(); + let GitoxideFeatures { + fetch, + shallow_index, + checkout, + shallow_deps, + internal_use_git2, + } = &mut out; + + for e in it { + match e.as_ref() { + "fetch" => *fetch = true, + "shallow-index" => *shallow_index = true, + "shallow-deps" => *shallow_deps = true, + "checkout" => *checkout = true, + "internal-use-git2" => *internal_use_git2 = true, + _ => { + bail!("unstable 'gitoxide' only takes `fetch`, 'shallow-index', 'shallow-deps' and 'checkout' as valid inputs") + } + } + } + Ok(Some(out)) +} + fn parse_check_cfg( it: impl Iterator>, ) -> CargoResult> { @@ -879,6 +945,10 @@ impl CliUnstable { for flag in flags { self.add(flag, &mut warnings)?; } + + if self.gitoxide.is_none() && cargo_use_gitoxide_instead_of_git2() { + self.gitoxide = GitoxideFeatures::safe().into(); + } Ok(warnings) } @@ -948,6 +1018,7 @@ impl CliUnstable { "no-index-update" => self.no_index_update = parse_empty(k, v)?, "avoid-dev-deps" => self.avoid_dev_deps = parse_empty(k, v)?, "minimal-versions" => self.minimal_versions = parse_empty(k, v)?, + "direct-minimal-versions" => self.direct_minimal_versions = parse_empty(k, v)?, "advanced-env" => self.advanced_env = parse_empty(k, v)?, "config-include" => self.config_include = parse_empty(k, v)?, "check-cfg" => { @@ -967,6 +1038,12 @@ impl CliUnstable { "doctest-in-workspace" => self.doctest_in_workspace = parse_empty(k, v)?, "panic-abort-tests" => self.panic_abort_tests = parse_empty(k, v)?, "jobserver-per-rustc" => self.jobserver_per_rustc = parse_empty(k, v)?, + "gitoxide" => { + self.gitoxide = v.map_or_else( + || Ok(Some(GitoxideFeatures::all())), + |v| parse_gitoxide(v.split(',')), + )? + } "host-config" => self.host_config = parse_empty(k, v)?, "target-applies-to-host" => self.target_applies_to_host = parse_empty(k, v)?, "publish-timeout" => self.publish_timeout = parse_empty(k, v)?, @@ -995,12 +1072,7 @@ impl CliUnstable { "multitarget" => stabilized_warn(k, "1.64", STABILISED_MULTITARGET), "rustdoc-map" => self.rustdoc_map = parse_empty(k, v)?, "terminal-width" => stabilized_warn(k, "1.68", STABILIZED_TERMINAL_WIDTH), - "sparse-registry" => { - // Once sparse-registry becomes the default for crates.io, `sparse_registry` should - // be removed entirely from `CliUnstable`. - stabilized_warn(k, "1.68", STABILISED_SPARSE_REGISTRY); - self.sparse_registry = parse_empty(k, v)?; - } + "sparse-registry" => stabilized_warn(k, "1.68", STABILISED_SPARSE_REGISTRY), "registry-auth" => self.registry_auth = parse_empty(k, v)?, "namespaced-features" => stabilized_warn(k, "1.60", STABILISED_NAMESPACED_FEATURES), "weak-dep-features" => stabilized_warn(k, "1.60", STABILIZED_WEAK_DEP_FEATURES), @@ -1096,9 +1168,15 @@ impl CliUnstable { /// Returns the current release channel ("stable", "beta", "nightly", "dev"). pub fn channel() -> String { + // ALLOWED: For testing cargo itself only. + #[allow(clippy::disallowed_methods)] if let Ok(override_channel) = env::var("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS") { return override_channel; } + // ALLOWED: the process of rustc boostrapping reads this through + // `std::env`. We should make the behavior consistent. Also, we + // don't advertise this for bypassing nightly. + #[allow(clippy::disallowed_methods)] if let Ok(staging) = env::var("RUSTC_BOOTSTRAP") { if staging == "1" { return "dev".to_string(); @@ -1108,3 +1186,12 @@ pub fn channel() -> String { .release_channel .unwrap_or_else(|| String::from("dev")) } + +/// Only for testing and developing. See ["Running with gitoxide as default git backend in tests"][1]. +/// +/// [1]: https://doc.crates.io/contrib/tests/running.html#running-with-gitoxide-as-default-git-backend-in-tests +// ALLOWED: For testing cargo itself only. +#[allow(clippy::disallowed_methods)] +fn cargo_use_gitoxide_instead_of_git2() -> bool { + std::env::var_os("__CARGO_USE_GITOXIDE_INSTEAD_OF_GIT2").map_or(false, |value| value == "1") +} diff --git a/src/cargo/core/package.rs b/src/cargo/core/package.rs index 952b7cba26d..40ba9cdf894 100644 --- a/src/cargo/core/package.rs +++ b/src/cargo/core/package.rs @@ -26,9 +26,10 @@ use crate::core::{Dependency, Manifest, PackageId, SourceId, Target}; use crate::core::{SourceMap, Summary, Workspace}; use crate::ops; use crate::util::config::PackageCacheLock; -use crate::util::errors::{CargoResult, HttpNotSuccessful}; +use crate::util::errors::{CargoResult, HttpNotSuccessful, DEBUG_HEADERS}; use crate::util::interning::InternedString; -use crate::util::network::Retry; +use crate::util::network::retry::{Retry, RetryResult}; +use crate::util::network::sleep::SleepTracker; use crate::util::{self, internal, Config, Progress, ProgressStyle}; pub const MANIFEST_PREAMBLE: &str = "\ @@ -319,6 +320,8 @@ pub struct Downloads<'a, 'cfg> { /// Set of packages currently being downloaded. This should stay in sync /// with `pending`. pending_ids: HashSet, + /// Downloads that have failed and are waiting to retry again later. + sleeping: SleepTracker<(Download<'cfg>, Easy)>, /// The final result of each download. A pair `(token, result)`. This is a /// temporary holding area, needed because curl can report multiple /// downloads at once, but the main loop (`wait`) is written to only @@ -376,6 +379,9 @@ struct Download<'cfg> { /// Actual downloaded data, updated throughout the lifetime of this download. data: RefCell>, + /// HTTP headers for debugging. + headers: RefCell>, + /// The URL that we're downloading from, cached here for error messages and /// reenqueuing. url: String, @@ -442,6 +448,7 @@ impl<'cfg> PackageSet<'cfg> { next: 0, pending: HashMap::new(), pending_ids: HashSet::new(), + sleeping: SleepTracker::new(), results: Vec::new(), progress: RefCell::new(Some(Progress::with_style( "Downloading", @@ -758,6 +765,19 @@ impl<'a, 'cfg> Downloads<'a, 'cfg> { }); Ok(buf.len()) })?; + handle.header_function(move |data| { + tls::with(|downloads| { + if let Some(downloads) = downloads { + // Headers contain trailing \r\n, trim them to make it easier + // to work with. + let h = String::from_utf8_lossy(data).trim().to_string(); + if DEBUG_HEADERS.iter().any(|p| h.starts_with(p)) { + downloads.pending[&token].0.headers.borrow_mut().push(h); + } + } + }); + true + })?; handle.progress(true)?; handle.progress_function(move |dl_total, dl_cur, _, _| { @@ -783,6 +803,7 @@ impl<'a, 'cfg> Downloads<'a, 'cfg> { let dl = Download { token, data: RefCell::new(Vec::new()), + headers: RefCell::new(Vec::new()), id, url, descriptor, @@ -800,7 +821,7 @@ impl<'a, 'cfg> Downloads<'a, 'cfg> { /// Returns the number of crates that are still downloading. pub fn remaining(&self) -> usize { - self.pending.len() + self.pending.len() + self.sleeping.len() } /// Blocks the current thread waiting for a package to finish downloading. @@ -822,6 +843,7 @@ impl<'a, 'cfg> Downloads<'a, 'cfg> { .remove(&token) .expect("got a token for a non-in-progress transfer"); let data = mem::take(&mut *dl.data.borrow_mut()); + let headers = mem::take(&mut *dl.headers.borrow_mut()); let mut handle = self.set.multi.remove(handle)?; self.pending_ids.remove(&dl.id); @@ -831,51 +853,52 @@ impl<'a, 'cfg> Downloads<'a, 'cfg> { let ret = { let timed_out = &dl.timed_out; let url = &dl.url; - dl.retry - .r#try(|| { - if let Err(e) = result { - // If this error is "aborted by callback" then that's - // probably because our progress callback aborted due to - // a timeout. We'll find out by looking at the - // `timed_out` field, looking for a descriptive message. - // If one is found we switch the error code (to ensure - // it's flagged as spurious) and then attach our extra - // information to the error. - if !e.is_aborted_by_callback() { - return Err(e.into()); - } - - return Err(match timed_out.replace(None) { - Some(msg) => { - let code = curl_sys::CURLE_OPERATION_TIMEDOUT; - let mut err = curl::Error::new(code); - err.set_extra(msg); - err - } - None => e, - } - .into()); + dl.retry.r#try(|| { + if let Err(e) = result { + // If this error is "aborted by callback" then that's + // probably because our progress callback aborted due to + // a timeout. We'll find out by looking at the + // `timed_out` field, looking for a descriptive message. + // If one is found we switch the error code (to ensure + // it's flagged as spurious) and then attach our extra + // information to the error. + if !e.is_aborted_by_callback() { + return Err(e.into()); } - let code = handle.response_code()?; - if code != 200 && code != 0 { - let url = handle.effective_url()?.unwrap_or(url); - return Err(HttpNotSuccessful { - code, - url: url.to_string(), - body: data, + return Err(match timed_out.replace(None) { + Some(msg) => { + let code = curl_sys::CURLE_OPERATION_TIMEDOUT; + let mut err = curl::Error::new(code); + err.set_extra(msg); + err } - .into()); + None => e, } - Ok(data) - }) - .with_context(|| format!("failed to download from `{}`", dl.url))? + .into()); + } + + let code = handle.response_code()?; + if code != 200 && code != 0 { + return Err(HttpNotSuccessful::new_from_handle( + &mut handle, + &url, + data, + headers, + ) + .into()); + } + Ok(data) + }) }; match ret { - Some(data) => break (dl, data), - None => { - self.pending_ids.insert(dl.id); - self.enqueue(dl, handle)? + RetryResult::Success(data) => break (dl, data), + RetryResult::Err(e) => { + return Err(e.context(format!("failed to download from `{}`", dl.url))) + } + RetryResult::Retry(sleep) => { + debug!("download retry {} for {sleep}ms", dl.url); + self.sleeping.push(sleep, (dl, handle)); } } }; @@ -963,6 +986,7 @@ impl<'a, 'cfg> Downloads<'a, 'cfg> { // actually block waiting for I/O to happen, which we achieve with the // `wait` method on `multi`. loop { + self.add_sleepers()?; let n = tls::set(self, || { self.set .multi @@ -985,15 +1009,29 @@ impl<'a, 'cfg> Downloads<'a, 'cfg> { if let Some(pair) = results.pop() { break Ok(pair); } - assert!(!self.pending.is_empty()); - let min_timeout = Duration::new(1, 0); - let timeout = self.set.multi.get_timeout()?.unwrap_or(min_timeout); - let timeout = timeout.min(min_timeout); - self.set - .multi - .wait(&mut [], timeout) - .with_context(|| "failed to wait on curl `Multi`")?; + assert_ne!(self.remaining(), 0); + if self.pending.is_empty() { + let delay = self.sleeping.time_to_next().unwrap(); + debug!("sleeping main thread for {delay:?}"); + std::thread::sleep(delay); + } else { + let min_timeout = Duration::new(1, 0); + let timeout = self.set.multi.get_timeout()?.unwrap_or(min_timeout); + let timeout = timeout.min(min_timeout); + self.set + .multi + .wait(&mut [], timeout) + .with_context(|| "failed to wait on curl `Multi`")?; + } + } + } + + fn add_sleepers(&mut self) -> CargoResult<()> { + for (dl, handle) in self.sleeping.to_retry() { + self.pending_ids.insert(dl.id); + self.enqueue(dl, handle)?; } + Ok(()) } fn progress(&self, token: usize, total: u64, cur: u64) -> bool { @@ -1061,7 +1099,7 @@ impl<'a, 'cfg> Downloads<'a, 'cfg> { return Ok(()); } } - let pending = self.pending.len(); + let pending = self.remaining(); let mut msg = if pending == 1 { format!("{} crate", pending) } else { diff --git a/src/cargo/core/resolver/dep_cache.rs b/src/cargo/core/resolver/dep_cache.rs index dd86170035e..4fd27538555 100644 --- a/src/cargo/core/resolver/dep_cache.rs +++ b/src/cargo/core/resolver/dep_cache.rs @@ -36,9 +36,13 @@ pub struct RegistryQueryer<'a> { /// versions first. That allows `cargo update -Z minimal-versions` which will /// specify minimum dependency versions to be used. minimal_versions: bool, - /// a cache of `Candidate`s that fulfil a `Dependency` - registry_cache: HashMap>>>, + /// a cache of `Candidate`s that fulfil a `Dependency` (and whether `first_minimal_version`) + registry_cache: HashMap<(Dependency, bool), Poll>>>, /// a cache of `Dependency`s that are required for a `Summary` + /// + /// HACK: `first_minimal_version` is not kept in the cache key is it is 1:1 with + /// `parent.is_none()` (the first element of the cache key) as it doesn't change through + /// execution. summary_cache: HashMap< (Option, Summary, ResolveOpts), (Rc<(HashSet, Rc>)>, bool), @@ -96,8 +100,13 @@ impl<'a> RegistryQueryer<'a> { /// any candidates are returned which match an override then the override is /// applied by performing a second query for what the override should /// return. - pub fn query(&mut self, dep: &Dependency) -> Poll>>> { - if let Some(out) = self.registry_cache.get(dep).cloned() { + pub fn query( + &mut self, + dep: &Dependency, + first_minimal_version: bool, + ) -> Poll>>> { + let registry_cache_key = (dep.clone(), first_minimal_version); + if let Some(out) = self.registry_cache.get(®istry_cache_key).cloned() { return out.map(Result::Ok); } @@ -106,7 +115,8 @@ impl<'a> RegistryQueryer<'a> { ret.push(s); })?; if ready.is_pending() { - self.registry_cache.insert(dep.clone(), Poll::Pending); + self.registry_cache + .insert((dep.clone(), first_minimal_version), Poll::Pending); return Poll::Pending; } for summary in ret.iter() { @@ -128,7 +138,8 @@ impl<'a> RegistryQueryer<'a> { let mut summaries = match self.registry.query_vec(dep, QueryKind::Exact)? { Poll::Ready(s) => s.into_iter(), Poll::Pending => { - self.registry_cache.insert(dep.clone(), Poll::Pending); + self.registry_cache + .insert((dep.clone(), first_minimal_version), Poll::Pending); return Poll::Pending; } }; @@ -192,18 +203,18 @@ impl<'a> RegistryQueryer<'a> { // When we attempt versions for a package we'll want to do so in a sorted fashion to pick // the "best candidates" first. VersionPreferences implements this notion. - self.version_prefs.sort_summaries( - &mut ret, - if self.minimal_versions { - VersionOrdering::MinimumVersionsFirst - } else { - VersionOrdering::MaximumVersionsFirst - }, - ); + let ordering = if first_minimal_version || self.minimal_versions { + VersionOrdering::MinimumVersionsFirst + } else { + VersionOrdering::MaximumVersionsFirst + }; + let first_version = first_minimal_version; + self.version_prefs + .sort_summaries(&mut ret, ordering, first_version); let out = Poll::Ready(Rc::new(ret)); - self.registry_cache.insert(dep.clone(), out.clone()); + self.registry_cache.insert(registry_cache_key, out.clone()); out.map(Result::Ok) } @@ -218,6 +229,7 @@ impl<'a> RegistryQueryer<'a> { parent: Option, candidate: &Summary, opts: &ResolveOpts, + first_minimal_version: bool, ) -> ActivateResult, Rc>)>> { // if we have calculated a result before, then we can just return it, // as it is a "pure" query of its arguments. @@ -237,22 +249,24 @@ impl<'a> RegistryQueryer<'a> { let mut all_ready = true; let mut deps = deps .into_iter() - .filter_map(|(dep, features)| match self.query(&dep) { - Poll::Ready(Ok(candidates)) => Some(Ok((dep, candidates, features))), - Poll::Pending => { - all_ready = false; - // we can ignore Pending deps, resolve will be repeatedly called - // until there are none to ignore - None - } - Poll::Ready(Err(e)) => Some(Err(e).with_context(|| { - format!( - "failed to get `{}` as a dependency of {}", - dep.package_name(), - describe_path_in_context(cx, &candidate.package_id()), - ) - })), - }) + .filter_map( + |(dep, features)| match self.query(&dep, first_minimal_version) { + Poll::Ready(Ok(candidates)) => Some(Ok((dep, candidates, features))), + Poll::Pending => { + all_ready = false; + // we can ignore Pending deps, resolve will be repeatedly called + // until there are none to ignore + None + } + Poll::Ready(Err(e)) => Some(Err(e).with_context(|| { + format!( + "failed to get `{}` as a dependency of {}", + dep.package_name(), + describe_path_in_context(cx, &candidate.package_id()), + ) + })), + }, + ) .collect::>>()?; // Attempt to resolve dependencies with fewer candidates before trying diff --git a/src/cargo/core/resolver/mod.rs b/src/cargo/core/resolver/mod.rs index b0551891da1..b9c29fb872b 100644 --- a/src/cargo/core/resolver/mod.rs +++ b/src/cargo/core/resolver/mod.rs @@ -37,6 +37,17 @@ //! everything to bail out immediately and return success, and only if *nothing* //! works do we actually return an error up the stack. //! +//! Resolution is currently performed twice +//! 1. With all features enabled (this is what gets saved to `Cargo.lock`) +//! 2. With only the specific features the user selected on the command-line. Ideally this +//! run will get removed in the future when transitioning to the new feature resolver. +//! +//! A new feature-specific resolver was added in 2020 which adds more sophisticated feature +//! resolution. It is located in the [`features`] module. The original dependency resolver still +//! performs feature unification, as it can help reduce the dependencies it has to consider during +//! resolution (rather than assuming every optional dependency of every package is enabled). +//! Checking if a feature is enabled must go through the new feature resolver. +//! //! ## Performance //! //! Note that this is a relatively performance-critical portion of Cargo. The @@ -78,7 +89,7 @@ pub use self::version_prefs::{VersionOrdering, VersionPreferences}; mod conflict_cache; mod context; mod dep_cache; -mod encode; +pub(crate) mod encode; pub(crate) mod errors; pub mod features; mod resolve; @@ -133,11 +144,21 @@ pub fn resolve( Some(config) => config.cli_unstable().minimal_versions, None => false, }; + let direct_minimal_versions = match config { + Some(config) => config.cli_unstable().direct_minimal_versions, + None => false, + }; let mut registry = RegistryQueryer::new(registry, replacements, version_prefs, minimal_versions); let cx = loop { let cx = Context::new(check_public_visible_dependencies); - let cx = activate_deps_loop(cx, &mut registry, summaries, config)?; + let cx = activate_deps_loop( + cx, + &mut registry, + summaries, + direct_minimal_versions, + config, + )?; if registry.reset_pending() { break cx; } else { @@ -189,6 +210,7 @@ fn activate_deps_loop( mut cx: Context, registry: &mut RegistryQueryer<'_>, summaries: &[(Summary, ResolveOpts)], + direct_minimal_versions: bool, config: Option<&Config>, ) -> CargoResult { let mut backtrack_stack = Vec::new(); @@ -201,7 +223,14 @@ fn activate_deps_loop( // Activate all the initial summaries to kick off some work. for &(ref summary, ref opts) in summaries { debug!("initial activation: {}", summary.package_id()); - let res = activate(&mut cx, registry, None, summary.clone(), opts); + let res = activate( + &mut cx, + registry, + None, + summary.clone(), + direct_minimal_versions, + opts, + ); match res { Ok(Some((frame, _))) => remaining_deps.push(frame), Ok(None) => (), @@ -399,7 +428,15 @@ fn activate_deps_loop( dep.package_name(), candidate.version() ); - let res = activate(&mut cx, registry, Some((&parent, &dep)), candidate, &opts); + let direct_minimal_version = false; // this is an indirect dependency + let res = activate( + &mut cx, + registry, + Some((&parent, &dep)), + candidate, + direct_minimal_version, + &opts, + ); let successfully_activated = match res { // Success! We've now activated our `candidate` in our context @@ -611,6 +648,7 @@ fn activate( registry: &mut RegistryQueryer<'_>, parent: Option<(&Summary, &Dependency)>, candidate: Summary, + first_minimal_version: bool, opts: &ResolveOpts, ) -> ActivateResult> { let candidate_pid = candidate.package_id(); @@ -662,8 +700,13 @@ fn activate( }; let now = Instant::now(); - let (used_features, deps) = - &*registry.build_deps(cx, parent.map(|p| p.0.package_id()), &candidate, opts)?; + let (used_features, deps) = &*registry.build_deps( + cx, + parent.map(|p| p.0.package_id()), + &candidate, + opts, + first_minimal_version, + )?; // Record what list of features is active for this package. if !used_features.is_empty() { @@ -811,10 +854,8 @@ impl RemainingCandidates { } } -/// Attempts to find a new conflict that allows a `find_candidate` feather then the input one. +/// Attempts to find a new conflict that allows a `find_candidate` better then the input one. /// It will add the new conflict to the cache if one is found. -/// -/// Panics if the input conflict is not all active in `cx`. fn generalize_conflicting( cx: &Context, registry: &mut RegistryQueryer<'_>, @@ -823,15 +864,12 @@ fn generalize_conflicting( dep: &Dependency, conflicting_activations: &ConflictMap, ) -> Option { - if conflicting_activations.is_empty() { - return None; - } // We need to determine the `ContextAge` that this `conflicting_activations` will jump to, and why. - let (backtrack_critical_age, backtrack_critical_id) = conflicting_activations - .keys() - .map(|&c| (cx.is_active(c).expect("not currently active!?"), c)) - .max() - .unwrap(); + let (backtrack_critical_age, backtrack_critical_id) = shortcircuit_max( + conflicting_activations + .keys() + .map(|&c| cx.is_active(c).map(|a| (a, c))), + )?; let backtrack_critical_reason: ConflictReason = conflicting_activations[&backtrack_critical_id].clone(); @@ -856,11 +894,14 @@ fn generalize_conflicting( }) { for critical_parents_dep in critical_parents_deps.iter() { + // We only want `first_minimal_version=true` for direct dependencies of workspace + // members which isn't the case here as this has a `parent` + let first_minimal_version = false; // A dep is equivalent to one of the things it can resolve to. // Thus, if all the things it can resolve to have already ben determined // to be conflicting, then we can just say that we conflict with the parent. if let Some(others) = registry - .query(critical_parents_dep) + .query(critical_parents_dep, first_minimal_version) .expect("an already used dep now error!?") .expect("an already used dep now pending!?") .iter() @@ -923,6 +964,19 @@ fn generalize_conflicting( None } +/// Returns Some of the largest item in the iterator. +/// Returns None if any of the items are None or the iterator is empty. +fn shortcircuit_max(iter: impl Iterator>) -> Option { + let mut out = None; + for i in iter { + if i.is_none() { + return None; + } + out = std::cmp::max(out, i); + } + out +} + /// Looks through the states in `backtrack_stack` for dependencies with /// remaining candidates. For each one, also checks if rolling back /// could change the outcome of the failed resolution that caused backtracking @@ -949,12 +1003,10 @@ fn find_candidate( // the cause of that backtrack, so we do not update it. let age = if !backtracked { // we don't have abnormal situations. So we can ask `cx` for how far back we need to go. - let a = cx.is_conflicting(Some(parent.package_id()), conflicting_activations); - // If the `conflicting_activations` does not apply to `cx`, then something went very wrong - // in building it. But we will just fall back to laboriously trying all possibilities witch - // will give us the correct answer so only `assert` if there is a developer to debug it. - debug_assert!(a.is_some()); - a + // If the `conflicting_activations` does not apply to `cx`, + // we will just fall back to laboriously trying all possibilities witch + // will give us the correct answer. + cx.is_conflicting(Some(parent.package_id()), conflicting_activations) } else { None }; diff --git a/src/cargo/core/resolver/types.rs b/src/cargo/core/resolver/types.rs index 6ea5b796a31..40bdb6c211d 100644 --- a/src/cargo/core/resolver/types.rs +++ b/src/cargo/core/resolver/types.rs @@ -15,6 +15,9 @@ pub struct ResolverProgress { time_to_print: Duration, printed: bool, deps_time: Duration, + /// Provides an escape hatch for machine with slow CPU for debugging and + /// testing Cargo itself. + /// See [rust-lang/cargo#6596](https://github.com/rust-lang/cargo/pull/6596) for more. #[cfg(debug_assertions)] slow_cpu_multiplier: u64, } @@ -31,6 +34,9 @@ impl ResolverProgress { // Architectures that do not have a modern processor, hardware emulation, etc. // In the test code we have `slow_cpu_multiplier`, but that is not accessible here. #[cfg(debug_assertions)] + // ALLOWED: For testing cargo itself only. However, it was communicated as an public + // interface to other developers, so keep it as-is, shouldn't add `__CARGO` prefix. + #[allow(clippy::disallowed_methods)] slow_cpu_multiplier: std::env::var("CARGO_TEST_SLOW_CPU_MULTIPLIER") .ok() .and_then(|m| m.parse().ok()) diff --git a/src/cargo/core/resolver/version_prefs.rs b/src/cargo/core/resolver/version_prefs.rs index 8eb800c4058..73cce5db87a 100644 --- a/src/cargo/core/resolver/version_prefs.rs +++ b/src/cargo/core/resolver/version_prefs.rs @@ -42,7 +42,12 @@ impl VersionPreferences { /// Sort the given vector of summaries in-place, with all summaries presumed to be for /// the same package. Preferred versions appear first in the result, sorted by /// `version_ordering`, followed by non-preferred versions sorted the same way. - pub fn sort_summaries(&self, summaries: &mut Vec, version_ordering: VersionOrdering) { + pub fn sort_summaries( + &self, + summaries: &mut Vec, + version_ordering: VersionOrdering, + first_version: bool, + ) { let should_prefer = |pkg_id: &PackageId| { self.try_to_use.contains(pkg_id) || self @@ -66,6 +71,9 @@ impl VersionPreferences { _ => previous_cmp, } }); + if first_version { + let _ = summaries.split_off(1); + } } } @@ -115,13 +123,13 @@ mod test { summ("foo", "1.0.9"), ]; - vp.sort_summaries(&mut summaries, VersionOrdering::MaximumVersionsFirst); + vp.sort_summaries(&mut summaries, VersionOrdering::MaximumVersionsFirst, false); assert_eq!( describe(&summaries), "foo/1.2.3, foo/1.2.4, foo/1.1.0, foo/1.0.9".to_string() ); - vp.sort_summaries(&mut summaries, VersionOrdering::MinimumVersionsFirst); + vp.sort_summaries(&mut summaries, VersionOrdering::MinimumVersionsFirst, false); assert_eq!( describe(&summaries), "foo/1.2.3, foo/1.0.9, foo/1.1.0, foo/1.2.4".to_string() @@ -140,13 +148,13 @@ mod test { summ("foo", "1.0.9"), ]; - vp.sort_summaries(&mut summaries, VersionOrdering::MaximumVersionsFirst); + vp.sort_summaries(&mut summaries, VersionOrdering::MaximumVersionsFirst, false); assert_eq!( describe(&summaries), "foo/1.2.3, foo/1.2.4, foo/1.1.0, foo/1.0.9".to_string() ); - vp.sort_summaries(&mut summaries, VersionOrdering::MinimumVersionsFirst); + vp.sort_summaries(&mut summaries, VersionOrdering::MinimumVersionsFirst, false); assert_eq!( describe(&summaries), "foo/1.2.3, foo/1.0.9, foo/1.1.0, foo/1.2.4".to_string() @@ -166,13 +174,13 @@ mod test { summ("foo", "1.0.9"), ]; - vp.sort_summaries(&mut summaries, VersionOrdering::MaximumVersionsFirst); + vp.sort_summaries(&mut summaries, VersionOrdering::MaximumVersionsFirst, false); assert_eq!( describe(&summaries), "foo/1.2.3, foo/1.1.0, foo/1.2.4, foo/1.0.9".to_string() ); - vp.sort_summaries(&mut summaries, VersionOrdering::MinimumVersionsFirst); + vp.sort_summaries(&mut summaries, VersionOrdering::MinimumVersionsFirst, false); assert_eq!( describe(&summaries), "foo/1.1.0, foo/1.2.3, foo/1.0.9, foo/1.2.4".to_string() diff --git a/src/cargo/core/shell.rs b/src/cargo/core/shell.rs index 79f09e6f9ce..fdae617c4f8 100644 --- a/src/cargo/core/shell.rs +++ b/src/cargo/core/shell.rs @@ -17,6 +17,8 @@ impl TtyWidth { /// Returns the width of the terminal to use for diagnostics (which is /// relayed to rustc via `--diagnostic-width`). pub fn diagnostic_terminal_width(&self) -> Option { + // ALLOWED: For testing cargo itself only. + #[allow(clippy::disallowed_methods)] if let Ok(width) = std::env::var("__CARGO_TEST_TTY_WIDTH_DO_NOT_USE_THIS") { return Some(width.parse().unwrap()); } diff --git a/src/cargo/core/source/mod.rs b/src/cargo/core/source/mod.rs index 13c32d2dd5c..dca71b64e9d 100644 --- a/src/cargo/core/source/mod.rs +++ b/src/cargo/core/source/mod.rs @@ -44,6 +44,9 @@ pub trait Source { /// Ensure that the source is fully up-to-date for the current session on the next query. fn invalidate_cache(&mut self); + /// If quiet, the source should not display any progress or status messages. + fn set_quiet(&mut self, quiet: bool); + /// Fetches the full package for each name and version specified. fn download(&mut self, package: PackageId) -> CargoResult; @@ -163,6 +166,10 @@ impl<'a, T: Source + ?Sized + 'a> Source for Box { (**self).invalidate_cache() } + fn set_quiet(&mut self, quiet: bool) { + (**self).set_quiet(quiet) + } + /// Forwards to `Source::download`. fn download(&mut self, id: PackageId) -> CargoResult { (**self).download(id) @@ -233,6 +240,10 @@ impl<'a, T: Source + ?Sized + 'a> Source for &'a mut T { (**self).invalidate_cache() } + fn set_quiet(&mut self, quiet: bool) { + (**self).set_quiet(quiet) + } + fn download(&mut self, id: PackageId) -> CargoResult { (**self).download(id) } diff --git a/src/cargo/core/source/source_id.rs b/src/cargo/core/source/source_id.rs index 5faa95d3f8b..034d7ed590c 100644 --- a/src/cargo/core/source/source_id.rs +++ b/src/cargo/core/source/source_id.rs @@ -254,7 +254,7 @@ impl SourceId { "unsupported registry protocol `{unknown}` (defined in {})", proto.as_ref().unwrap().definition ), - None => config.cli_unstable().sparse_registry, + None => true, }; Ok(is_sparse) } @@ -428,9 +428,7 @@ impl SourceId { _ => return false, } let url = self.inner.url.as_str(); - url == CRATES_IO_INDEX - || url == CRATES_IO_HTTP_INDEX - || std::env::var("__CARGO_TEST_CRATES_IO_URL_DO_NOT_USE_THIS").as_deref() == Ok(url) + url == CRATES_IO_INDEX || url == CRATES_IO_HTTP_INDEX || is_overridden_crates_io_url(url) } /// Hashes `self`. @@ -884,3 +882,10 @@ mod tests { assert_eq!(source_id, deserialized); } } + +/// Check if `url` equals to the overridden crates.io URL. +// ALLOWED: For testing Cargo itself only. +#[allow(clippy::disallowed_methods)] +fn is_overridden_crates_io_url(url: &str) -> bool { + std::env::var("__CARGO_TEST_CRATES_IO_URL_DO_NOT_USE_THIS").map_or(false, |v| v == url) +} diff --git a/src/cargo/lib.rs b/src/cargo/lib.rs index 9f14e5381bd..f4c671fd3ba 100644 --- a/src/cargo/lib.rs +++ b/src/cargo/lib.rs @@ -4,58 +4,124 @@ // Due to some of the default clippy lints being somewhat subjective and not // necessarily an improvement, we prefer to not use them at this time. #![allow(clippy::all)] +#![warn(clippy::disallowed_methods)] #![warn(clippy::self_named_module_files)] #![allow(rustdoc::private_intra_doc_links)] //! # Cargo as a library //! -//! Cargo, the Rust package manager, is also provided as a library. -//! //! There are two places you can find API documentation of cargo-the-library, //! -//! - and -//! - . -//! -//! Each of them targets on a slightly different audience. +//! - : targeted at external tool developers using cargo-the-library +//! - Released with every rustc release +//! - : targeted at cargo contributors +//! - Updated on each update of the `cargo` submodule in `rust-lang/rust` //! -//! ## For external tool developers +//! **WARNING:** Using Cargo as a library has drawbacks, particulary the API is unstable, +//! and there is no clear path to stabilize it soon at the time of writing. See [The Cargo Book: +//! External tools] for more on this topic. //! -//! The documentation on contains public-facing items in cargo-the-library. -//! External tool developers may find it useful when trying to reuse existing building blocks from Cargo. -//! However, using Cargo as a library has drawbacks, especially cargo-the-library is unstable, -//! and there is no clear path to stabilize it soon at the time of writing. -//! See [The Cargo Book: External tools] for more on this topic. +//! ## Overview //! -//! Cargo API documentation on docs.rs gets updates along with each Rust release. -//! Its version always has a 0 major version to state it is unstable. -//! The minor version is always +1 of rustc's minor version -//! (that is, `cargo 0.66.0` corresponds to `rustc 1.65`). +//! Major components of cargo include: //! -//! ## For Cargo contributors +//! - [`ops`]: +//! Every major operation is implemented here. Each command is a thin wrapper around ops. +//! - [`ops::cargo_compile`]: +//! This is the entry point for all the compilation commands. This is a +//! good place to start if you want to follow how compilation starts and +//! flows to completion. +//! - [`ops::resolve`]: +//! Top-level API for dependency and feature resolver (e.g. [`ops::resolve_ws`]) +//! - [`core::resolver`]: The core algorithm +//! - [`core::compiler`]: +//! This is the code responsible for running `rustc` and `rustdoc`. +//! - [`core::compiler::build_context`]: +//! The [`BuildContext`]['core::compiler::BuildContext] is the result of the "front end" of the +//! build process. This contains the graph of work to perform and any settings necessary for +//! `rustc`. After this is built, the next stage of building is handled in +//! [`Context`][core::compiler::Context]. +//! - [`core::compiler::context`]: +//! The `Context` is the mutable state used during the build process. This +//! is the core of the build process, and everything is coordinated through +//! this. +//! - [`core::compiler::fingerprint`]: +//! The `fingerprint` module contains all the code that handles detecting +//! if a crate needs to be recompiled. +//! - [`core::source`]: +//! The [`core::Source`] trait is an abstraction over different sources of packages. +//! Sources are uniquely identified by a [`core::SourceId`]. Sources are implemented in the [`sources`] +//! directory. +//! - [`util`]: +//! This directory contains generally-useful utility modules. +//! - [`util::config`]: +//! This directory contains the config parser. It makes heavy use of +//! [serde](https://serde.rs/) to merge and translate config values. The +//! [`util::Config`] is usually accessed from the +//! [`core::Workspace`] +//! though references to it are scattered around for more convenient access. +//! - [`util::toml`]: +//! This directory contains the code for parsing `Cargo.toml` files. +//! - [`ops::lockfile`]: +//! This is where `Cargo.lock` files are loaded and saved. //! -//! The documentation on contains all items in Cargo. -//! Contributors of Cargo may find it useful as a reference of Cargo's implementation details. -//! It's built with `--document-private-items` rustdoc flag, -//! so you might expect to see some noise and strange items here. -//! The Cargo team and contributors strive for jotting down every details -//! from their brains in each issue and PR. -//! However, something might just disappear in the air with no reason. -//! This documentation can be seen as their extended minds, -//! sharing designs and hacks behind both public and private interfaces. +//! Related crates: +//! - [`cargo-platform`](https://crates.io/crates/cargo-platform) +//! ([nightly docs](https://doc.rust-lang.org/nightly/nightly-rustc/cargo_platform)): +//! This library handles parsing `cfg` expressions. +//! - [`cargo-util`](https://crates.io/crates/cargo-util) +//! ([nightly docs](https://doc.rust-lang.org/nightly/nightly-rustc/cargo_util)): +//! This contains general utility code that is shared between cargo and the testsuite +//! - [`crates-io`](https://crates.io/crates/crates-io) +//! ([nightly docs](https://doc.rust-lang.org/nightly/nightly-rustc/crates_io)): +//! This contains code for accessing the crates.io API. +//! - [`home`](https://crates.io/crates/home): +//! This library is shared between cargo and rustup and is used for finding their home directories. +//! This is not directly depended upon with a `path` dependency; cargo uses the version from crates.io. +//! It is intended to be versioned and published independently of Rust's release system. +//! Whenever a change needs to be made, bump the version in Cargo.toml and `cargo publish` it manually, and then update cargo's `Cargo.toml` to depend on the new version. +//! - [`cargo-test-support`](https://github.com/rust-lang/cargo/tree/master/crates/cargo-test-support) +//! ([nightly docs](https://doc.rust-lang.org/nightly/nightly-rustc/cargo_test_support/index.html)): +//! This contains a variety of code to support writing tests +//! - [`cargo-test-macro`](https://github.com/rust-lang/cargo/tree/master/crates/cargo-test-macro) +//! ([nightly docs](https://doc.rust-lang.org/nightly/nightly-rustc/cargo_test_macro/index.html)): +//! This is the `#[cargo_test]` proc-macro used by the test suite to define tests. +//! - [`credential`](https://github.com/rust-lang/cargo/tree/master/crates/credential) +//! This subdirectory contains several packages for implementing the +//! experimental +//! [credential-process](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#credential-process) +//! feature. +//! - [`mdman`](https://github.com/rust-lang/cargo/tree/master/crates/mdman) +//! ([nightly docs](https://doc.rust-lang.org/nightly/nightly-rustc/mdman/index.html)): +//! This is a utility for generating cargo's man pages. See [Building the man +//! pages](https://github.com/rust-lang/cargo/tree/master/src/doc#building-the-man-pages) +//! for more information. +//! - [`resolver-tests`](https://github.com/rust-lang/cargo/tree/master/crates/resolver-tests) +//! This is a dedicated package that defines tests for the [dependency +//! resolver][core::resolver]. //! -//! If you are just diving into Cargo internals, [Cargo Architecture Overview] -//! is the best material to get a broader context of how Cargo works under the hood. -//! Things also worth a read are important concepts reside in source code, -//! which Cargo developers have been crafting for a while, namely +//! ### File Overview //! -//! - [`cargo::core::resolver`](crate::core::resolver), -//! - [`cargo::core::compiler::fingerprint`](core/compiler/fingerprint/index.html), -//! - [`cargo::util::config`](crate::util::config), -//! - [`cargo::ops::fix`](ops/fix/index.html), and -//! - [`cargo::sources::registry`](crate::sources::registry). +//! Files that interact with cargo include //! -//! This API documentation is published on each push of rust-lang/cargo master branch. -//! In other words, it always reflects the latest doc comments in source code on master branch. +//! - Package +//! - `Cargo.toml`: User-written project manifest, loaded with [`util::toml::TomlManifest`] and then +//! translated to [`core::manifest::Manifest`] which maybe stored in a [`core::Package`]. +//! - This is editable with [`util::toml_mut::manifest::LocalManifest`] +//! - `Cargo.lock`: Generally loaded with [`ops::resolve_ws`] or a variant of it into a [`core::resolver::Resolve`] +//! - At the lowest level, [`ops::load_pkg_lockfile`] and [`ops::write_pkg_lockfile`] are used +//! - See [`core::resolver::encode`] for versioning of `Cargo.lock` +//! - `target/`: Used for build artifacts and abstracted with [`core::compiler::layout`]. `Layout` handles locking the target directory and providing paths to parts inside. There is a separate `Layout` for each build `target`. +//! - `target/debug/.fingerprint`: Tracker whether nor not a crate needs to be rebuilt. See [`core::compiler::fingerprint`] +//! - `$CARGO_HOME/`: +//! - `registry/`: Package registry cache which is managed in [`sources::registry`]. Be careful +//! as the lock [`util::Config::acquire_package_cache_lock`] must be manually acquired. +//! - `index`/: Fast-to-access crate metadata (no need to download / extract `*.crate` files) +//! - `cache/*/*.crate`: Local cache of published crates +//! - `src/*/*`: Extracted from `*.crate` by [`sources::registry::RegistrySource`] +//! - `git/`: Git source cache. See [`sources::git`]. +//! - `**/.cargo/config.toml`: Environment dependent (env variables, files) configuration. See +//! [`util::config`] //! //! ## Contribute to Cargo documentations //! diff --git a/src/cargo/ops/cargo_add/mod.rs b/src/cargo/ops/cargo_add/mod.rs index c22466399e7..5c519ac09f0 100644 --- a/src/cargo/ops/cargo_add/mod.rs +++ b/src/cargo/ops/cargo_add/mod.rs @@ -726,7 +726,8 @@ impl DependencyUI { .get(next) .into_iter() .flatten() - .map(|s| s.as_str()), + .map(|s| s.as_str()) + .filter(|s| !activated.contains(s)), ); activated.extend( self.available_features diff --git a/src/cargo/ops/cargo_compile/mod.rs b/src/cargo/ops/cargo_compile/mod.rs index 2113422b3ac..3b6043d4fdb 100644 --- a/src/cargo/ops/cargo_compile/mod.rs +++ b/src/cargo/ops/cargo_compile/mod.rs @@ -6,20 +6,26 @@ //! The [`compile`] function will do all the work to compile a workspace. A //! rough outline is: //! -//! - Resolve the dependency graph (see [`ops::resolve`]). -//! - Download any packages needed (see [`PackageSet`](crate::core::PackageSet)). -//! - Generate a list of top-level "units" of work for the targets the user +//! 1. Resolve the dependency graph (see [`ops::resolve`]). +//! 2. Download any packages needed (see [`PackageSet`](crate::core::PackageSet)). +//! 3. Generate a list of top-level "units" of work for the targets the user //! requested on the command-line. Each [`Unit`] corresponds to a compiler //! invocation. This is done in this module ([`UnitGenerator::generate_root_units`]). -//! - Build the graph of `Unit` dependencies (see [`unit_dependencies`]). -//! - Create a [`Context`] which will perform the following steps: -//! - Prepare the `target` directory (see [`Layout`]). -//! - Create a job queue (see `JobQueue`). The queue checks the +//! 4. Starting from the root [`Unit`]s, generate the [`UnitGraph`] by walking the dependency graph +//! from the resolver. See also [`unit_dependencies`]. +//! 5. Construct the [`BuildContext`] with all of the information collected so +//! far. This is the end of the "front end" of compilation. +//! 6. Create a [`Context`] which coordinates the compilation process +//! and will perform the following steps: +//! 1. Prepare the `target` directory (see [`Layout`]). +//! 2. Create a [`JobQueue`]. The queue checks the //! fingerprint of each `Unit` to determine if it should run or be //! skipped. -//! - Execute the queue. Each leaf in the queue's dependency graph is -//! executed, and then removed from the graph when finished. This -//! repeats until the queue is empty. +//! 3. Execute the queue via [`drain_the_queue`]. Each leaf in the queue's dependency graph is +//! executed, and then removed from the graph when finished. This repeats until the queue is +//! empty. Note that this is the only point in cargo that currently uses threads. +//! 7. The result of the compilation is stored in the [`Compilation`] struct. This can be used for +//! various things, such as running tests after the compilation has finished. //! //! **Note**: "target" inside this module generally refers to ["Cargo Target"], //! which corresponds to artifact that will be built in a package. Not to be @@ -27,6 +33,8 @@ //! //! [`unit_dependencies`]: crate::core::compiler::unit_dependencies //! [`Layout`]: crate::core::compiler::Layout +//! [`JobQueue`]: crate::core::compiler::job_queue +//! [`drain_the_queue`]: crate::core::compiler::job_queue //! ["Cargo Target"]: https://doc.rust-lang.org/nightly/cargo/reference/cargo-targets.html use std::collections::{HashMap, HashSet}; diff --git a/src/cargo/ops/cargo_generate_lockfile.rs b/src/cargo/ops/cargo_generate_lockfile.rs index 04d4010f45e..6267b08f588 100644 --- a/src/cargo/ops/cargo_generate_lockfile.rs +++ b/src/cargo/ops/cargo_generate_lockfile.rs @@ -8,7 +8,7 @@ use crate::util::CargoResult; use anyhow::Context; use log::debug; use std::collections::{BTreeMap, HashSet}; -use termcolor::Color::{self, Cyan, Green, Red}; +use termcolor::Color::{self, Cyan, Green, Red, Yellow}; pub struct UpdateOptions<'a> { pub config: &'a Config, @@ -142,7 +142,12 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes } else { format!("{} -> v{}", removed[0], added[0].version()) }; - print_change("Updating", msg, Green)?; + + if removed[0].version() > added[0].version() { + print_change("Downgrading", msg, Yellow)?; + } else { + print_change("Updating", msg, Green)?; + } } else { for package in removed.iter() { print_change("Removing", format!("{}", package), Red)?; diff --git a/src/cargo/ops/cargo_new.rs b/src/cargo/ops/cargo_new.rs index 7fa66736474..caa1d2fa8cc 100644 --- a/src/cargo/ops/cargo_new.rs +++ b/src/cargo/ops/cargo_new.rs @@ -514,31 +514,31 @@ pub fn init(opts: &NewOptions, config: &Config) -> CargoResult { let mut version_control = opts.version_control; if version_control == None { - let mut num_detected_vsces = 0; + let mut num_detected_vcses = 0; if path.join(".git").exists() { version_control = Some(VersionControl::Git); - num_detected_vsces += 1; + num_detected_vcses += 1; } if path.join(".hg").exists() { version_control = Some(VersionControl::Hg); - num_detected_vsces += 1; + num_detected_vcses += 1; } if path.join(".pijul").exists() { version_control = Some(VersionControl::Pijul); - num_detected_vsces += 1; + num_detected_vcses += 1; } if path.join(".fossil").exists() { version_control = Some(VersionControl::Fossil); - num_detected_vsces += 1; + num_detected_vcses += 1; } // if none exists, maybe create git, like in `cargo new` - if num_detected_vsces > 1 { + if num_detected_vcses > 1 { anyhow::bail!( "more than one of .hg, .git, .pijul, .fossil configurations \ found and the ignore file can't be filled in as \ @@ -742,7 +742,7 @@ fn mk(config: &Config, opts: &MkOptions<'_>) -> CargoResult<()> { // Using the push method with multiple arguments ensures that the entries // for all mutually-incompatible VCS in terms of syntax are in sync. let mut ignore = IgnoreList::new(); - ignore.push("/target", "^target/", "target"); + ignore.push("/target", "^target$", "target"); if !opts.bin { ignore.push("/Cargo.lock", "^Cargo.lock$", "Cargo.lock"); } diff --git a/src/cargo/ops/cargo_run.rs b/src/cargo/ops/cargo_run.rs index 69bae2c5912..53916715a9f 100644 --- a/src/cargo/ops/cargo_run.rs +++ b/src/cargo/ops/cargo_run.rs @@ -93,6 +93,11 @@ pub fn run( }; let pkg = bins[0].0; let mut process = compile.target_process(exe, unit.kind, pkg, *script_meta)?; + + // Sets the working directory of the child process to the current working + // directory of the parent process. + // Overrides the default working directory of the `ProcessBuilder` returned + // by `compile.target_process` (the package's root directory) process.args(args).cwd(config.cwd()); config.shell().status("Running", process.to_string())?; diff --git a/src/cargo/ops/common_for_install_and_uninstall.rs b/src/cargo/ops/common_for_install_and_uninstall.rs index 81083c7fbf8..f33847d5767 100644 --- a/src/cargo/ops/common_for_install_and_uninstall.rs +++ b/src/cargo/ops/common_for_install_and_uninstall.rs @@ -616,9 +616,10 @@ where let examples = candidates .iter() .filter(|cand| cand.targets().iter().filter(|t| t.is_example()).count() > 0); - let pkg = match one(binaries, |v| multi_err("binaries", v))? { + let git_url = source.source_id().url().to_string(); + let pkg = match one(binaries, |v| multi_err("binaries", &git_url, v))? { Some(p) => p, - None => match one(examples, |v| multi_err("examples", v))? { + None => match one(examples, |v| multi_err("examples", &git_url, v))? { Some(p) => p, None => bail!( "no packages found with binaries or \ @@ -629,17 +630,20 @@ where Ok(pkg.clone()) }; - fn multi_err(kind: &str, mut pkgs: Vec<&Package>) -> String { + fn multi_err(kind: &str, git_url: &str, mut pkgs: Vec<&Package>) -> String { pkgs.sort_unstable_by_key(|a| a.name()); + let first_pkg = pkgs[0]; format!( "multiple packages with {} found: {}. When installing a git repository, \ - cargo will always search the entire repo for any Cargo.toml. \ - Please specify which to install.", + cargo will always search the entire repo for any Cargo.toml.\n\ + Please specify a package, e.g. `cargo install --git {} {}`.", kind, pkgs.iter() .map(|p| p.name().as_str()) .collect::>() - .join(", ") + .join(", "), + git_url, + first_pkg.name() ) } } diff --git a/src/cargo/ops/fix.rs b/src/cargo/ops/fix.rs index 9cc2fddad28..be24967f8b9 100644 --- a/src/cargo/ops/fix.rs +++ b/src/cargo/ops/fix.rs @@ -17,10 +17,10 @@ //! Cargo begins a normal `cargo check` operation with itself set as a proxy //! for rustc by setting `primary_unit_rustc` in the build config. When //! cargo launches rustc to check a crate, it is actually launching itself. -//! The `FIX_ENV` environment variable is set so that cargo knows it is in +//! The `FIX_ENV_INTERNAL` environment variable is set so that cargo knows it is in //! fix-proxy-mode. //! -//! Each proxied cargo-as-rustc detects it is in fix-proxy-mode (via `FIX_ENV` +//! Each proxied cargo-as-rustc detects it is in fix-proxy-mode (via `FIX_ENV_INTERNAL` //! environment variable in `main`) and does the following: //! //! - Acquire a lock from the `LockServer` from the master cargo process. @@ -63,10 +63,20 @@ use crate::util::Config; use crate::util::{existing_vcs_repo, LockServer, LockServerClient}; use crate::{drop_eprint, drop_eprintln}; -const FIX_ENV: &str = "__CARGO_FIX_PLZ"; -const BROKEN_CODE_ENV: &str = "__CARGO_FIX_BROKEN_CODE"; -const EDITION_ENV: &str = "__CARGO_FIX_EDITION"; -const IDIOMS_ENV: &str = "__CARGO_FIX_IDIOMS"; +/// **Internal only.** +/// Indicates Cargo is in fix-proxy-mode if presents. +/// The value of it is the socket address of the [`LockServer`] being used. +/// See the [module-level documentation](mod@super::fix) for more. +const FIX_ENV_INTERNAL: &str = "__CARGO_FIX_PLZ"; +/// **Internal only.** +/// For passing [`FixOptions::broken_code`] through to cargo running in proxy mode. +const BROKEN_CODE_ENV_INTERNAL: &str = "__CARGO_FIX_BROKEN_CODE"; +/// **Internal only.** +/// For passing [`FixOptions::edition`] through to cargo running in proxy mode. +const EDITION_ENV_INTERNAL: &str = "__CARGO_FIX_EDITION"; +/// **Internal only.** +/// For passing [`FixOptions::idioms`] through to cargo running in proxy mode. +const IDIOMS_ENV_INTERNAL: &str = "__CARGO_FIX_IDIOMS"; pub struct FixOptions { pub edition: bool, @@ -87,20 +97,20 @@ pub fn fix(ws: &Workspace<'_>, opts: &mut FixOptions) -> CargoResult<()> { // Spin up our lock server, which our subprocesses will use to synchronize fixes. let lock_server = LockServer::new()?; let mut wrapper = ProcessBuilder::new(env::current_exe()?); - wrapper.env(FIX_ENV, lock_server.addr().to_string()); + wrapper.env(FIX_ENV_INTERNAL, lock_server.addr().to_string()); let _started = lock_server.start()?; opts.compile_opts.build_config.force_rebuild = true; if opts.broken_code { - wrapper.env(BROKEN_CODE_ENV, "1"); + wrapper.env(BROKEN_CODE_ENV_INTERNAL, "1"); } if opts.edition { - wrapper.env(EDITION_ENV, "1"); + wrapper.env(EDITION_ENV_INTERNAL, "1"); } if opts.idioms { - wrapper.env(IDIOMS_ENV, "1"); + wrapper.env(IDIOMS_ENV_INTERNAL, "1"); } *opts @@ -339,7 +349,10 @@ to prevent this issue from happening. /// Returns `None` if `fix` is not being run (not in proxy mode). Returns /// `Some(...)` if in `fix` proxy mode pub fn fix_get_proxy_lock_addr() -> Option { - env::var(FIX_ENV).ok() + // ALLOWED: For the internal mechanism of `cargo fix` only. + // Shouldn't be set directly by anyone. + #[allow(clippy::disallowed_methods)] + env::var(FIX_ENV_INTERNAL).ok() } /// Entry point for `cargo` running as a proxy for `rustc`. @@ -360,7 +373,7 @@ pub fn fix_exec_rustc(config: &Config, lock_addr: &str) -> CargoResult<()> { .ok(); let mut rustc = ProcessBuilder::new(&args.rustc).wrapped(workspace_rustc.as_ref()); rustc.retry_with_argfile(true); - rustc.env_remove(FIX_ENV); + rustc.env_remove(FIX_ENV_INTERNAL); args.apply(&mut rustc); trace!("start rustfixing {:?}", args.file); @@ -404,7 +417,7 @@ pub fn fix_exec_rustc(config: &Config, lock_addr: &str) -> CargoResult<()> { // user's code with our changes. Back out everything and fall through // below to recompile again. if !output.status.success() { - if config.get_env_os(BROKEN_CODE_ENV).is_none() { + if config.get_env_os(BROKEN_CODE_ENV_INTERNAL).is_none() { for (path, file) in fixes.files.iter() { debug!("reverting {:?} due to errors", path); paths::write(path, &file.original_code)?; @@ -578,7 +591,7 @@ fn rustfix_and_fix( // worse by applying fixes where a bug could cause *more* broken code. // Instead, punt upwards which will reexec rustc over the original code, // displaying pretty versions of the diagnostics we just read out. - if !output.status.success() && config.get_env_os(BROKEN_CODE_ENV).is_none() { + if !output.status.success() && config.get_env_os(BROKEN_CODE_ENV_INTERNAL).is_none() { debug!( "rustfixing `{:?}` failed, rustc exited with {:?}", filename, @@ -847,9 +860,15 @@ impl FixArgs { } let file = file.ok_or_else(|| anyhow::anyhow!("could not find .rs file in rustc args"))?; - let idioms = env::var(IDIOMS_ENV).is_ok(); - - let prepare_for_edition = env::var(EDITION_ENV).ok().map(|_| { + // ALLOWED: For the internal mechanism of `cargo fix` only. + // Shouldn't be set directly by anyone. + #[allow(clippy::disallowed_methods)] + let idioms = env::var(IDIOMS_ENV_INTERNAL).is_ok(); + + // ALLOWED: For the internal mechanism of `cargo fix` only. + // Shouldn't be set directly by anyone. + #[allow(clippy::disallowed_methods)] + let prepare_for_edition = env::var(EDITION_ENV_INTERNAL).ok().map(|_| { enabled_edition .unwrap_or(Edition::Edition2015) .saturating_next() diff --git a/src/cargo/ops/mod.rs b/src/cargo/ops/mod.rs index d04cb84716c..4b6aea991bd 100644 --- a/src/cargo/ops/mod.rs +++ b/src/cargo/ops/mod.rs @@ -34,7 +34,7 @@ pub use self::vendor::{vendor, VendorOptions}; pub mod cargo_add; mod cargo_clean; -mod cargo_compile; +pub(crate) mod cargo_compile; pub mod cargo_config; mod cargo_doc; mod cargo_fetch; @@ -51,9 +51,9 @@ mod cargo_test; mod cargo_uninstall; mod common_for_install_and_uninstall; mod fix; -mod lockfile; -mod registry; -mod resolve; +pub(crate) mod lockfile; +pub(crate) mod registry; +pub(crate) mod resolve; pub mod tree; mod vendor; diff --git a/src/cargo/ops/registry.rs b/src/cargo/ops/registry.rs index edbd2d4cd52..e04f7ba2cff 100644 --- a/src/cargo/ops/registry.rs +++ b/src/cargo/ops/registry.rs @@ -36,6 +36,7 @@ use crate::util::config::{Config, SslVersionConfig, SslVersionConfigRange}; use crate::util::errors::CargoResult; use crate::util::important_paths::find_root_manifest_for_wd; use crate::util::{truncate_with_ellipsis, IntoUrl}; +use crate::util::{Progress, ProgressStyle}; use crate::{drop_print, drop_println, version}; /// Registry settings loaded from config files. @@ -442,13 +443,29 @@ fn wait_for_publish( ) -> CargoResult<()> { let version_req = format!("={}", pkg.version()); let mut source = SourceConfigMap::empty(config)?.load(registry_src, &HashSet::new())?; - let source_description = source.describe(); + // Disable the source's built-in progress bars. Repeatedly showing a bunch + // of independent progress bars can be a little confusing. There is an + // overall progress bar managed here. + source.set_quiet(true); + let source_description = source.source_id().to_string(); let query = Dependency::parse(pkg.name(), Some(&version_req), registry_src)?; let now = std::time::Instant::now(); let sleep_time = std::time::Duration::from_secs(1); - let mut logged = false; - loop { + let max = timeout.as_secs() as usize; + // Short does not include the registry name. + let short_pkg_description = format!("{} v{}", pkg.name(), pkg.version()); + config.shell().status( + "Uploaded", + format!("{short_pkg_description} to {source_description}"), + )?; + config.shell().note(format!( + "Waiting for `{short_pkg_description}` to be available at {source_description}.\n\ + You may press ctrl-c to skip waiting; the crate should be available shortly." + ))?; + let mut progress = Progress::with_style("Waiting", ProgressStyle::Ratio, config); + progress.tick_now(0, max, "")?; + let is_available = loop { { let _lock = config.acquire_package_cache_lock()?; // Force re-fetching the source @@ -470,31 +487,30 @@ fn wait_for_publish( } }; if !summaries.is_empty() { - break; + break true; } } - if timeout < now.elapsed() { + let elapsed = now.elapsed(); + if timeout < elapsed { config.shell().warn(format!( - "timed out waiting for `{}` to be in {}", - pkg.name(), - source_description + "timed out waiting for `{short_pkg_description}` to be available in {source_description}", ))?; - break; - } - - if !logged { - config.shell().status( - "Waiting", - format!( - "on `{}` to propagate to {} (ctrl-c to wait asynchronously)", - pkg.name(), - source_description - ), + config.shell().note( + "The registry may have a backlog that is delaying making the \ + crate available. The crate should be available soon.", )?; - logged = true; + break false; } + + progress.tick_now(elapsed.as_secs() as usize, max, "")?; std::thread::sleep(sleep_time); + }; + if is_available { + config.shell().status( + "Published", + format!("{short_pkg_description} at {source_description}"), + )?; } Ok(()) @@ -619,9 +635,6 @@ pub fn configure_http_handle(config: &Config, handle: &mut Easy) -> CargoResult< handle.useragent(&format!("cargo {}", version()))?; } - // Empty string accept encoding expands to the encodings supported by the current libcurl. - handle.accept_encoding("")?; - fn to_ssl_version(s: &str) -> CargoResult { let version = match s { "default" => SslVersion::Default, @@ -631,13 +644,15 @@ pub fn configure_http_handle(config: &Config, handle: &mut Easy) -> CargoResult< "tlsv1.2" => SslVersion::Tlsv12, "tlsv1.3" => SslVersion::Tlsv13, _ => bail!( - "Invalid ssl version `{}`,\ - choose from 'default', 'tlsv1', 'tlsv1.0', 'tlsv1.1', 'tlsv1.2', 'tlsv1.3'.", - s + "Invalid ssl version `{s}`,\ + choose from 'default', 'tlsv1', 'tlsv1.0', 'tlsv1.1', 'tlsv1.2', 'tlsv1.3'." ), }; Ok(version) } + + // Empty string accept encoding expands to the encodings supported by the current libcurl. + handle.accept_encoding("")?; if let Some(ssl_version) = &http.ssl_version { match ssl_version { SslVersionConfig::Single(s) => { @@ -939,6 +954,20 @@ pub fn registry_logout(config: &Config, reg: Option<&str>) -> CargoResult<()> { reg_name ), )?; + let location = if source_ids.original.is_crates_io() { + "".to_string() + } else { + // The URL for the source requires network access to load the config. + // That could be a fairly heavy operation to perform just to provide a + // help message, so for now this just provides some generic text. + // Perhaps in the future this could have an API to fetch the config if + // it is cached, but avoid network access otherwise? + format!("the `{reg_name}` website") + }; + config.shell().note(format!( + "This does not revoke the token on the registry server.\n \ + If you need to revoke the token, visit {location} and follow the instructions there." + ))?; Ok(()) } diff --git a/src/cargo/ops/resolve.rs b/src/cargo/ops/resolve.rs index c3d1bb4f365..ea5eded4aa2 100644 --- a/src/cargo/ops/resolve.rs +++ b/src/cargo/ops/resolve.rs @@ -1,14 +1,59 @@ //! High-level APIs for executing the resolver. //! -//! This module provides functions for running the resolver given a workspace. +//! This module provides functions for running the resolver given a workspace, including loading +//! the `Cargo.lock` file and checkinf if it needs updating. +//! //! There are roughly 3 main functions: //! -//! - `resolve_ws`: A simple, high-level function with no options. -//! - `resolve_ws_with_opts`: A medium-level function with options like +//! - [`resolve_ws`]: A simple, high-level function with no options. +//! - [`resolve_ws_with_opts`]: A medium-level function with options like //! user-provided features. This is the most appropriate function to use in //! most cases. -//! - `resolve_with_previous`: A low-level function for running the resolver, +//! - [`resolve_with_previous`]: A low-level function for running the resolver, //! providing the most power and flexibility. +//! +//! ### Data Structures +//! +//! - [`Workspace`]: +//! Usually created by [`crate::util::command_prelude::ArgMatchesExt::workspace`] which discovers the root of the +//! workspace, and loads all the workspace members as a [`Package`] object +//! - [`Package`] +//! Corresponds with `Cargo.toml` manifest (deserialized as [`Manifest`]) and its associated files. +//! - [`Target`]s are crates such as the library, binaries, integration test, or examples. +//! They are what is actually compiled by `rustc`. +//! Each `Target` defines a crate root, like `src/lib.rs` or `examples/foo.rs`. +//! - [`PackageId`] --- A unique identifier for a package. +//! - [`PackageRegistry`]: +//! The primary interface for how the dependency +//! resolver finds packages. It contains the `SourceMap`, and handles things +//! like the `[patch]` table. The dependency resolver +//! sends a query to the `PackageRegistry` to "get me all packages that match +//! this dependency declaration". The `Registry` trait provides a generic interface +//! to the `PackageRegistry`, but this is only used for providing an alternate +//! implementation of the `PackageRegistry` for testing. +//! - [`SourceMap`]: Map of all available sources. +//! - [`Source`]: An abstraction for something that can fetch packages (a remote +//! registry, a git repo, the local filesystem, etc.). Check out the [source +//! implementations] for all the details about registries, indexes, git +//! dependencies, etc. +//! * [`SourceId`]: A unique identifier for a source. +//! - [`Summary`]: A of a [`Manifest`], and is essentially +//! the information that can be found in a registry index. Queries against the +//! `PackageRegistry` yields a `Summary`. The resolver uses the summary +//! information to build the dependency graph. +//! - [`PackageSet`] --- Contains all of the `Package` objects. This works with the +//! [`Downloads`] struct to coordinate downloading packages. It has a reference +//! to the `SourceMap` to get the `Source` objects which tell the `Downloads` +//! struct which URLs to fetch. +//! +//! [`Package`]: crate::core::package +//! [`Target`]: crate::core::Target +//! [`Manifest`]: crate::core::Manifest +//! [`Source`]: crate::core::Source +//! [`SourceMap`]: crate::core::SourceMap +//! [`PackageRegistry`]: crate::core::registry::PackageRegistry +//! [source implementations]: crate::sources +//! [`Downloads`]: crate::core::package::Downloads use crate::core::compiler::{CompileKind, RustcTargetData}; use crate::core::registry::{LockedPatchDependency, PackageRegistry}; diff --git a/src/cargo/ops/vendor.rs b/src/cargo/ops/vendor.rs index 8c507d87b91..3ee46db3284 100644 --- a/src/cargo/ops/vendor.rs +++ b/src/cargo/ops/vendor.rs @@ -4,7 +4,7 @@ use crate::core::{GitReference, Package, Workspace}; use crate::ops; use crate::sources::path::PathSource; use crate::sources::CRATES_IO_REGISTRY; -use crate::util::{CargoResult, Config}; +use crate::util::{try_canonicalize, CargoResult, Config}; use anyhow::{bail, Context as _}; use cargo_util::{paths, Sha256}; use serde::Serialize; @@ -83,7 +83,7 @@ fn sync( workspaces: &[&Workspace<'_>], opts: &VendorOptions<'_>, ) -> CargoResult { - let canonical_destination = opts.destination.canonicalize(); + let canonical_destination = try_canonicalize(opts.destination); let canonical_destination = canonical_destination.as_deref().unwrap_or(opts.destination); let dest_dir_already_exists = canonical_destination.exists(); @@ -125,7 +125,7 @@ fn sync( // Don't delete actual source code! if pkg.source_id().is_path() { if let Ok(path) = pkg.source_id().url().to_file_path() { - if let Ok(path) = path.canonicalize() { + if let Ok(path) = try_canonicalize(path) { to_remove.remove(&path); } } diff --git a/src/cargo/sources/directory.rs b/src/cargo/sources/directory.rs index e30755643a7..46acb9f8630 100644 --- a/src/cargo/sources/directory.rs +++ b/src/cargo/sources/directory.rs @@ -217,6 +217,10 @@ impl<'cfg> Source for DirectorySource<'cfg> { } fn invalidate_cache(&mut self) { - // Path source has no local cache. + // Directory source has no local cache. + } + + fn set_quiet(&mut self, _quiet: bool) { + // Directory source does not display status } } diff --git a/src/cargo/sources/git/known_hosts.rs b/src/cargo/sources/git/known_hosts.rs index 7916b07f595..9a623151ebb 100644 --- a/src/cargo/sources/git/known_hosts.rs +++ b/src/cargo/sources/git/known_hosts.rs @@ -20,6 +20,9 @@ //! and revoked markers. See "FIXME" comments littered in this file. use crate::util::config::{Config, Definition, Value}; +use base64::engine::general_purpose::STANDARD; +use base64::engine::general_purpose::STANDARD_NO_PAD; +use base64::Engine as _; use git2::cert::{Cert, SshHostKeyType}; use git2::CertificateCheckStatus; use hmac::Mac; @@ -40,6 +43,20 @@ use std::path::{Path, PathBuf}; static BUNDLED_KEYS: &[(&str, &str, &str)] = &[ ("github.com", "ssh-ed25519", "AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl"), ("github.com", "ecdsa-sha2-nistp256", "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg="), + ("github.com", "ssh-rsa", "AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk="), +]; + +/// List of keys that public hosts have rotated away from. +/// +/// We explicitly distrust these keys as users with the old key in their +/// local configuration will otherwise be vulnerable to MITM attacks if the +/// attacker has access to the old key. As there is no other way to distribute +/// revocations of ssh host keys, we need to bundle them with the client. +/// +/// Unlike [`BUNDLED_KEYS`], these revocations will not be ignored if the user +/// has their own entries: we *know* that these keys are bad. +static BUNDLED_REVOCATIONS: &[(&str, &str, &str)] = &[ + // Used until March 24, 2023: https://github.blog/2023-03-23-we-updated-our-rsa-ssh-host-key/ ("github.com", "ssh-rsa", "AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ=="), ]; @@ -344,7 +361,7 @@ fn check_ssh_known_hosts( .collect(); for (patterns, key_type, key) in BUNDLED_KEYS { if !configured_hosts.contains(*patterns) { - let key = base64::decode(key).unwrap(); + let key = STANDARD.decode(key).unwrap(); known_hosts.push(KnownHost { location: KnownHostLocation::Bundled, patterns: patterns.to_string(), @@ -354,6 +371,16 @@ fn check_ssh_known_hosts( }); } } + for (patterns, key_type, key) in BUNDLED_REVOCATIONS { + let key = STANDARD.decode(key).unwrap(); + known_hosts.push(KnownHost { + location: KnownHostLocation::Bundled, + patterns: patterns.to_string(), + key_type: key_type.to_string(), + key, + line_type: KnownHostLineType::Revoked, + }); + } check_ssh_known_hosts_loaded(&known_hosts, host, remote_key_type, remote_host_key) } @@ -382,9 +409,8 @@ fn check_ssh_known_hosts_loaded( // support SHA256. let mut remote_fingerprint = cargo_util::Sha256::new(); remote_fingerprint.update(remote_host_key.clone()); - let remote_fingerprint = - base64::encode_config(remote_fingerprint.finish(), base64::STANDARD_NO_PAD); - let remote_host_key_encoded = base64::encode(remote_host_key); + let remote_fingerprint = STANDARD_NO_PAD.encode(remote_fingerprint.finish()); + let remote_host_key_encoded = STANDARD.encode(remote_host_key); for known_host in known_hosts { // The key type from libgit2 needs to match the key type from the host file. @@ -583,8 +609,8 @@ impl KnownHost { fn hashed_hostname_matches(host: &str, hashed: &str) -> bool { let Some((b64_salt, b64_host)) = hashed.split_once('|') else { return false; }; - let Ok(salt) = base64::decode(b64_salt) else { return false; }; - let Ok(hashed_host) = base64::decode(b64_host) else { return false; }; + let Ok(salt) = STANDARD.decode(b64_salt) else { return false; }; + let Ok(hashed_host) = STANDARD.decode(b64_host) else { return false; }; let Ok(mut mac) = hmac::Hmac::::new_from_slice(&salt) else { return false; }; mac.update(host.as_bytes()); let result = mac.finalize().into_bytes(); @@ -636,7 +662,7 @@ fn parse_known_hosts_line(line: &str, location: KnownHostLocation) -> Option; +} diff --git a/src/cargo/sources/git/oxide.rs b/src/cargo/sources/git/oxide.rs new file mode 100644 index 00000000000..0270579da75 --- /dev/null +++ b/src/cargo/sources/git/oxide.rs @@ -0,0 +1,355 @@ +//! This module contains all code sporting `gitoxide` for operations on `git` repositories and it mirrors +//! `utils` closely for now. One day it can be renamed into `utils` once `git2` isn't required anymore. + +use crate::ops::HttpTimeout; +use crate::util::{human_readable_bytes, network, MetricsCounter, Progress}; +use crate::{CargoResult, Config}; +use cargo_util::paths; +use gix::bstr::{BString, ByteSlice}; +use log::debug; +use std::cell::RefCell; +use std::path::Path; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Weak}; +use std::time::{Duration, Instant}; + +/// For the time being, `repo_path` makes it easy to instantiate a gitoxide repo just for fetching. +/// In future this may change to be the gitoxide repository itself. +pub fn with_retry_and_progress( + repo_path: &std::path::Path, + config: &Config, + cb: &(dyn Fn( + &std::path::Path, + &AtomicBool, + &mut gix::progress::tree::Item, + &mut dyn FnMut(&gix::bstr::BStr), + ) -> Result<(), crate::sources::git::fetch::Error> + + Send + + Sync), +) -> CargoResult<()> { + std::thread::scope(|s| { + let mut progress_bar = Progress::new("Fetch", config); + network::retry::with_retry(config, || { + let progress_root: Arc = + gix::progress::tree::root::Options { + initial_capacity: 10, + message_buffer_capacity: 10, + } + .into(); + let root = Arc::downgrade(&progress_root); + let thread = s.spawn(move || { + let mut progress = progress_root.add_child("operation"); + let mut urls = RefCell::new(Default::default()); + let res = cb( + &repo_path, + &AtomicBool::default(), + &mut progress, + &mut |url| { + *urls.borrow_mut() = Some(url.to_owned()); + }, + ); + amend_authentication_hints(res, urls.get_mut().take()) + }); + translate_progress_to_bar(&mut progress_bar, root)?; + thread.join().expect("no panic in scoped thread") + }) + }) +} + +fn translate_progress_to_bar( + progress_bar: &mut Progress<'_>, + root: Weak, +) -> CargoResult<()> { + let read_pack_bytes: gix::progress::Id = + gix::odb::pack::bundle::write::ProgressId::ReadPackBytes.into(); + let delta_index_objects: gix::progress::Id = + gix::odb::pack::index::write::ProgressId::IndexObjects.into(); + let resolve_objects: gix::progress::Id = + gix::odb::pack::index::write::ProgressId::ResolveObjects.into(); + + // We choose `N=10` here to make a `300ms * 10slots ~= 3000ms` + // sliding window for tracking the data transfer rate (in bytes/s). + let mut last_percentage_update = Instant::now(); + let mut last_fast_update = Instant::now(); + let mut counter = MetricsCounter::<10>::new(0, last_percentage_update); + + let mut tasks = Vec::with_capacity(10); + let slow_check_interval = std::time::Duration::from_millis(300); + let fast_check_interval = Duration::from_millis(50); + let sleep_interval = Duration::from_millis(10); + debug_assert_eq!( + slow_check_interval.as_millis() % fast_check_interval.as_millis(), + 0, + "progress should be smoother by keeping these as multiples of each other" + ); + debug_assert_eq!( + fast_check_interval.as_millis() % sleep_interval.as_millis(), + 0, + "progress should be smoother by keeping these as multiples of each other" + ); + + while let Some(root) = root.upgrade() { + std::thread::sleep(sleep_interval); + let needs_update = last_fast_update.elapsed() >= fast_check_interval; + if !needs_update { + continue; + } + let now = Instant::now(); + last_fast_update = now; + + root.sorted_snapshot(&mut tasks); + + fn progress_by_id( + id: gix::progress::Id, + task: &gix::progress::Task, + ) -> Option<&gix::progress::Value> { + (task.id == id).then(|| task.progress.as_ref()).flatten() + } + fn find_in( + tasks: &[(K, gix::progress::Task)], + cb: impl Fn(&gix::progress::Task) -> Option<&gix::progress::Value>, + ) -> Option<&gix::progress::Value> { + tasks.iter().find_map(|(_, t)| cb(t)) + } + + const NUM_PHASES: usize = 2; // indexing + delta-resolution, both with same amount of objects to handle + if let Some(objs) = find_in(&tasks, |t| progress_by_id(resolve_objects, t)) { + // Resolving deltas. + let objects = objs.step.load(Ordering::Relaxed); + let total_objects = objs.done_at.expect("known amount of objects"); + let msg = format!(", ({objects}/{total_objects}) resolving deltas"); + + progress_bar.tick(total_objects + objects, total_objects * NUM_PHASES, &msg)?; + } else if let Some((objs, read_pack)) = + find_in(&tasks, |t| progress_by_id(read_pack_bytes, t)).and_then(|read| { + find_in(&tasks, |t| progress_by_id(delta_index_objects, t)) + .map(|delta| (delta, read)) + }) + { + // Receiving objects. + let objects = objs.step.load(Ordering::Relaxed); + let total_objects = objs.done_at.expect("known amount of objects"); + let received_bytes = read_pack.step.load(Ordering::Relaxed); + + let needs_percentage_update = last_percentage_update.elapsed() >= slow_check_interval; + if needs_percentage_update { + counter.add(received_bytes, now); + last_percentage_update = now; + } + let (rate, unit) = human_readable_bytes(counter.rate() as u64); + let msg = format!(", {rate:.2}{unit}/s"); + + progress_bar.tick(objects, total_objects * NUM_PHASES, &msg)?; + } + } + Ok(()) +} + +fn amend_authentication_hints( + res: Result<(), crate::sources::git::fetch::Error>, + last_url_for_authentication: Option, +) -> CargoResult<()> { + let Err(err) = res else { return Ok(()) }; + let e = match &err { + crate::sources::git::fetch::Error::PrepareFetch( + gix::remote::fetch::prepare::Error::RefMap(gix::remote::ref_map::Error::Handshake(err)), + ) => Some(err), + _ => None, + }; + if let Some(e) = e { + use anyhow::Context; + let auth_message = match e { + gix::protocol::handshake::Error::Credentials(_) => { + "\n* attempted to find username/password via \ + git's `credential.helper` support, but failed" + .into() + } + gix::protocol::handshake::Error::InvalidCredentials { .. } => { + "\n* attempted to find username/password via \ + `credential.helper`, but maybe the found \ + credentials were incorrect" + .into() + } + gix::protocol::handshake::Error::Transport(_) => { + let msg = concat!( + "network failure seems to have happened\n", + "if a proxy or similar is necessary `net.git-fetch-with-cli` may help here\n", + "https://doc.rust-lang.org/cargo/reference/config.html#netgit-fetch-with-cli" + ); + return Err(anyhow::Error::from(err)).context(msg); + } + _ => None, + }; + if let Some(auth_message) = auth_message { + let mut msg = "failed to authenticate when downloading \ + repository" + .to_string(); + if let Some(url) = last_url_for_authentication { + msg.push_str(": "); + msg.push_str(url.to_str_lossy().as_ref()); + } + msg.push('\n'); + msg.push_str(auth_message); + msg.push_str("\n\n"); + msg.push_str("if the git CLI succeeds then `net.git-fetch-with-cli` may help here\n"); + msg.push_str( + "https://doc.rust-lang.org/cargo/reference/config.html#netgit-fetch-with-cli", + ); + return Err(anyhow::Error::from(err)).context(msg); + } + } + Err(err.into()) +} + +/// The reason we are opening a git repository. +/// +/// This can affect the way we open it and the cost associated with it. +pub enum OpenMode { + /// We need `git_binary` configuration as well for being able to see credential helpers + /// that are configured with the `git` installation itself. + /// However, this is slow on windows (~150ms) and most people won't need it as they use the + /// standard index which won't ever need authentication, so we only enable this when needed. + ForFetch, +} + +impl OpenMode { + /// Sometimes we don't need to pay for figuring out the system's git installation, and this tells + /// us if that is the case. + pub fn needs_git_binary_config(&self) -> bool { + match self { + OpenMode::ForFetch => true, + } + } +} + +/// Produce a repository with everything pre-configured according to `config`. Most notably this includes +/// transport configuration. Knowing its `purpose` helps to optimize the way we open the repository. +/// Use `config_overrides` to configure the new repository. +pub fn open_repo( + repo_path: &std::path::Path, + config_overrides: Vec, + purpose: OpenMode, +) -> Result { + gix::open_opts(repo_path, { + let mut opts = gix::open::Options::default(); + opts.permissions.config = gix::permissions::Config::all(); + opts.permissions.config.git_binary = purpose.needs_git_binary_config(); + opts.with(gix::sec::Trust::Full) + .config_overrides(config_overrides) + }) +} + +/// Convert `git` related cargo configuration into the respective `git` configuration which can be +/// used when opening new repositories. +pub fn cargo_config_to_gitoxide_overrides(config: &Config) -> CargoResult> { + use gix::config::tree::{gitoxide, Core, Http, Key}; + let timeout = HttpTimeout::new(config)?; + let http = config.http_config()?; + + let mut values = vec![ + gitoxide::Http::CONNECT_TIMEOUT.validated_assignment_fmt(&timeout.dur.as_millis())?, + Http::LOW_SPEED_LIMIT.validated_assignment_fmt(&timeout.low_speed_limit)?, + Http::LOW_SPEED_TIME.validated_assignment_fmt(&timeout.dur.as_secs())?, + // Assure we are not depending on committer information when updating refs after cloning. + Core::LOG_ALL_REF_UPDATES.validated_assignment_fmt(&false)?, + ]; + if let Some(proxy) = &http.proxy { + values.push(Http::PROXY.validated_assignment_fmt(proxy)?); + } + if let Some(check_revoke) = http.check_revoke { + values.push(Http::SCHANNEL_CHECK_REVOKE.validated_assignment_fmt(&check_revoke)?); + } + if let Some(cainfo) = &http.cainfo { + values.push( + Http::SSL_CA_INFO.validated_assignment_fmt(&cainfo.resolve_path(config).display())?, + ); + } + + values.push(if let Some(user_agent) = &http.user_agent { + Http::USER_AGENT.validated_assignment_fmt(user_agent) + } else { + Http::USER_AGENT.validated_assignment_fmt(&format!("cargo {}", crate::version())) + }?); + if let Some(ssl_version) = &http.ssl_version { + use crate::util::config::SslVersionConfig; + match ssl_version { + SslVersionConfig::Single(version) => { + values.push(Http::SSL_VERSION.validated_assignment_fmt(&version)?); + } + SslVersionConfig::Range(range) => { + values.push( + gitoxide::Http::SSL_VERSION_MIN + .validated_assignment_fmt(&range.min.as_deref().unwrap_or("default"))?, + ); + values.push( + gitoxide::Http::SSL_VERSION_MAX + .validated_assignment_fmt(&range.max.as_deref().unwrap_or("default"))?, + ); + } + } + } else if cfg!(windows) { + // This text is copied from https://github.com/rust-lang/cargo/blob/39c13e67a5962466cc7253d41bc1099bbcb224c3/src/cargo/ops/registry.rs#L658-L674 . + // This is a temporary workaround for some bugs with libcurl and + // schannel and TLS 1.3. + // + // Our libcurl on Windows is usually built with schannel. + // On Windows 11 (or Windows Server 2022), libcurl recently (late + // 2022) gained support for TLS 1.3 with schannel, and it now defaults + // to 1.3. Unfortunately there have been some bugs with this. + // https://github.com/curl/curl/issues/9431 is the most recent. Once + // that has been fixed, and some time has passed where we can be more + // confident that the 1.3 support won't cause issues, this can be + // removed. + // + // Windows 10 is unaffected. libcurl does not support TLS 1.3 on + // Windows 10. (Windows 10 sorta had support, but it required enabling + // an advanced option in the registry which was buggy, and libcurl + // does runtime checks to prevent it.) + values.push(gitoxide::Http::SSL_VERSION_MIN.validated_assignment_fmt(&"default")?); + values.push(gitoxide::Http::SSL_VERSION_MAX.validated_assignment_fmt(&"tlsv1.2")?); + } + if let Some(debug) = http.debug { + values.push(gitoxide::Http::VERBOSE.validated_assignment_fmt(&debug)?); + } + if let Some(multiplexing) = http.multiplexing { + let http_version = multiplexing.then(|| "HTTP/2").unwrap_or("HTTP/1.1"); + // Note that failing to set the HTTP version in `gix-transport` isn't fatal, + // which is why we don't have to try to figure out if HTTP V2 is supported in the + // currently linked version (see `try_old_curl!()`) + values.push(Http::VERSION.validated_assignment_fmt(&http_version)?); + } + + Ok(values) +} + +pub fn reinitialize(git_dir: &Path) -> CargoResult<()> { + fn init(path: &Path, bare: bool) -> CargoResult<()> { + let mut opts = git2::RepositoryInitOptions::new(); + // Skip anything related to templates, they just call all sorts of issues as + // we really don't want to use them yet they insist on being used. See #6240 + // for an example issue that comes up. + opts.external_template(false); + opts.bare(bare); + git2::Repository::init_opts(&path, &opts)?; + Ok(()) + } + // Here we want to drop the current repository object pointed to by `repo`, + // so we initialize temporary repository in a sub-folder, blow away the + // existing git folder, and then recreate the git repo. Finally we blow away + // the `tmp` folder we allocated. + debug!("reinitializing git repo at {:?}", git_dir); + let tmp = git_dir.join("tmp"); + let bare = !git_dir.ends_with(".git"); + init(&tmp, false)?; + for entry in git_dir.read_dir()? { + let entry = entry?; + if entry.file_name().to_str() == Some("tmp") { + continue; + } + let path = entry.path(); + drop(paths::remove_file(&path).or_else(|_| paths::remove_dir_all(&path))); + } + init(git_dir, bare)?; + paths::remove_dir_all(&tmp)?; + Ok(()) +} diff --git a/src/cargo/sources/git/source.rs b/src/cargo/sources/git/source.rs index d09d5271627..90c47093dce 100644 --- a/src/cargo/sources/git/source.rs +++ b/src/cargo/sources/git/source.rs @@ -21,6 +21,7 @@ pub struct GitSource<'cfg> { path_source: Option>, ident: String, config: &'cfg Config, + quiet: bool, } impl<'cfg> GitSource<'cfg> { @@ -43,6 +44,7 @@ impl<'cfg> GitSource<'cfg> { path_source: None, ident, config, + quiet: false, }; Ok(source) @@ -162,10 +164,12 @@ impl<'cfg> Source for GitSource<'cfg> { self.remote.url() ); } - self.config.shell().status( - "Updating", - format!("git repository `{}`", self.remote.url()), - )?; + if !self.quiet { + self.config.shell().status( + "Updating", + format!("git repository `{}`", self.remote.url()), + )?; + } trace!("updating git source `{:?}`", self.remote); @@ -233,6 +237,10 @@ impl<'cfg> Source for GitSource<'cfg> { } fn invalidate_cache(&mut self) {} + + fn set_quiet(&mut self, quiet: bool) { + self.quiet = quiet; + } } #[cfg(test)] diff --git a/src/cargo/sources/git/utils.rs b/src/cargo/sources/git/utils.rs index 1979cf8a3a0..a7ffccf79a4 100644 --- a/src/cargo/sources/git/utils.rs +++ b/src/cargo/sources/git/utils.rs @@ -2,6 +2,8 @@ //! authentication/cloning. use crate::core::{GitReference, Verbosity}; +use crate::sources::git::oxide; +use crate::sources::git::oxide::cargo_config_to_gitoxide_overrides; use crate::util::errors::CargoResult; use crate::util::{human_readable_bytes, network, Config, IntoUrl, MetricsCounter, Progress}; use anyhow::{anyhow, Context as _}; @@ -16,6 +18,7 @@ use std::fmt; use std::path::{Path, PathBuf}; use std::process::Command; use std::str; +use std::sync::atomic::{AtomicBool, Ordering}; use std::time::{Duration, Instant}; use url::Url; @@ -564,7 +567,7 @@ where } // Whelp, we tried our best - Err(git2::Error::from_str("no authentication available")) + Err(git2::Error::from_str("no authentication methods succeeded")) }); // Ok, so if it looks like we're going to be doing ssh authentication, we @@ -609,7 +612,7 @@ where return git2::Cred::ssh_key_from_agent(&s); } } - Err(git2::Error::from_str("no authentication available")) + Err(git2::Error::from_str("no authentication methods succeeded")) }); // If we made two attempts then that means: @@ -736,7 +739,7 @@ pub fn with_fetch_options( let ssh_config = config.net_config()?.ssh.as_ref(); let config_known_hosts = ssh_config.and_then(|ssh| ssh.known_hosts.as_ref()); let diagnostic_home_config = config.diagnostic_home_config(); - network::with_retry(config, || { + network::retry::with_retry(config, || { with_authentication(config, url, git_config, |f| { let port = Url::parse(url).ok().and_then(|url| url.port()); let mut last_update = Instant::now(); @@ -802,7 +805,7 @@ pub fn with_fetch_options( pub fn fetch( repo: &mut git2::Repository, - url: &str, + orig_url: &str, reference: &GitReference, config: &Config, ) -> CargoResult<()> { @@ -818,7 +821,7 @@ pub fn fetch( // If we're fetching from GitHub, attempt GitHub's special fast path for // testing if we've already got an up-to-date copy of the repository - let oid_to_fetch = match github_fast_path(repo, url, reference, config) { + let oid_to_fetch = match github_fast_path(repo, orig_url, reference, config) { Ok(FastPathRev::UpToDate) => return Ok(()), Ok(FastPathRev::NeedsFetch(rev)) => Some(rev), Ok(FastPathRev::Indeterminate) => None, @@ -880,53 +883,157 @@ pub fn fetch( // flavors of authentication possible while also still giving us all the // speed and portability of using `libgit2`. if let Some(true) = config.net_config()?.git_fetch_with_cli { - return fetch_with_cli(repo, url, &refspecs, tags, config); + return fetch_with_cli(repo, orig_url, &refspecs, tags, config); } + if config + .cli_unstable() + .gitoxide + .map_or(false, |git| git.fetch) + { + let git2_repo = repo; + let config_overrides = cargo_config_to_gitoxide_overrides(config)?; + let repo_reinitialized = AtomicBool::default(); + let res = oxide::with_retry_and_progress( + &git2_repo.path().to_owned(), + config, + &|repo_path, + should_interrupt, + mut progress, + url_for_authentication: &mut dyn FnMut(&gix::bstr::BStr)| { + // The `fetch` operation here may fail spuriously due to a corrupt + // repository. It could also fail, however, for a whole slew of other + // reasons (aka network related reasons). We want Cargo to automatically + // recover from corrupt repositories, but we don't want Cargo to stomp + // over other legitimate errors. + // + // Consequently we save off the error of the `fetch` operation and if it + // looks like a "corrupt repo" error then we blow away the repo and try + // again. If it looks like any other kind of error, or if we've already + // blown away the repository, then we want to return the error as-is. + loop { + let res = oxide::open_repo( + repo_path, + config_overrides.clone(), + oxide::OpenMode::ForFetch, + ) + .map_err(crate::sources::git::fetch::Error::from) + .and_then(|repo| { + debug!("initiating fetch of {:?} from {}", refspecs, orig_url); + let url_for_authentication = &mut *url_for_authentication; + let remote = repo + .remote_at(orig_url)? + .with_fetch_tags(if tags { + gix::remote::fetch::Tags::All + } else { + gix::remote::fetch::Tags::Included + }) + .with_refspecs( + refspecs.iter().map(|s| s.as_str()), + gix::remote::Direction::Fetch, + ) + .map_err(crate::sources::git::fetch::Error::Other)?; + let url = remote + .url(gix::remote::Direction::Fetch) + .expect("set at init") + .to_owned(); + let connection = + remote.connect(gix::remote::Direction::Fetch, &mut progress)?; + let mut authenticate = connection.configured_credentials(url)?; + let connection = connection.with_credentials( + move |action: gix::protocol::credentials::helper::Action| { + if let Some(url) = action + .context() + .and_then(|ctx| ctx.url.as_ref().filter(|url| *url != orig_url)) + { + url_for_authentication(url.as_ref()); + } + authenticate(action) + }, + ); + let outcome = connection + .prepare_fetch(gix::remote::ref_map::Options::default())? + .receive(should_interrupt)?; + Ok(outcome) + }); + let err = match res { + Ok(_) => break, + Err(e) => e, + }; + debug!("fetch failed: {}", err); + + if !repo_reinitialized.load(Ordering::Relaxed) + // We check for errors that could occour if the configuration, refs or odb files are corrupted. + // We don't check for errors related to writing as `gitoxide` is expected to create missing leading + // folder before writing files into it, or else not even open a directory as git repository (which is + // also handled here). + && err.is_corrupted() + { + repo_reinitialized.store(true, Ordering::Relaxed); + debug!( + "looks like this is a corrupt repository, reinitializing \ + and trying again" + ); + if oxide::reinitialize(repo_path).is_ok() { + continue; + } + } - debug!("doing a fetch for {}", url); - let git_config = git2::Config::open_default()?; - with_fetch_options(&git_config, url, config, &mut |mut opts| { - if tags { - opts.download_tags(git2::AutotagOption::All); + return Err(err.into()); + } + Ok(()) + }, + ); + if repo_reinitialized.load(Ordering::Relaxed) { + *git2_repo = git2::Repository::open(git2_repo.path())?; } - // The `fetch` operation here may fail spuriously due to a corrupt - // repository. It could also fail, however, for a whole slew of other - // reasons (aka network related reasons). We want Cargo to automatically - // recover from corrupt repositories, but we don't want Cargo to stomp - // over other legitimate errors. - // - // Consequently we save off the error of the `fetch` operation and if it - // looks like a "corrupt repo" error then we blow away the repo and try - // again. If it looks like any other kind of error, or if we've already - // blown away the repository, then we want to return the error as-is. - let mut repo_reinitialized = false; - loop { - debug!("initiating fetch of {:?} from {}", refspecs, url); - let res = repo - .remote_anonymous(url)? - .fetch(&refspecs, Some(&mut opts), None); - let err = match res { - Ok(()) => break, - Err(e) => e, - }; - debug!("fetch failed: {}", err); - - if !repo_reinitialized && matches!(err.class(), ErrorClass::Reference | ErrorClass::Odb) - { - repo_reinitialized = true; - debug!( - "looks like this is a corrupt repository, reinitializing \ + res + } else { + debug!("doing a fetch for {}", orig_url); + let git_config = git2::Config::open_default()?; + with_fetch_options(&git_config, orig_url, config, &mut |mut opts| { + if tags { + opts.download_tags(git2::AutotagOption::All); + } + // The `fetch` operation here may fail spuriously due to a corrupt + // repository. It could also fail, however, for a whole slew of other + // reasons (aka network related reasons). We want Cargo to automatically + // recover from corrupt repositories, but we don't want Cargo to stomp + // over other legitimate errors. + // + // Consequently we save off the error of the `fetch` operation and if it + // looks like a "corrupt repo" error then we blow away the repo and try + // again. If it looks like any other kind of error, or if we've already + // blown away the repository, then we want to return the error as-is. + let mut repo_reinitialized = false; + loop { + debug!("initiating fetch of {:?} from {}", refspecs, orig_url); + let res = repo + .remote_anonymous(orig_url)? + .fetch(&refspecs, Some(&mut opts), None); + let err = match res { + Ok(()) => break, + Err(e) => e, + }; + debug!("fetch failed: {}", err); + + if !repo_reinitialized + && matches!(err.class(), ErrorClass::Reference | ErrorClass::Odb) + { + repo_reinitialized = true; + debug!( + "looks like this is a corrupt repository, reinitializing \ and trying again" - ); - if reinitialize(repo).is_ok() { - continue; + ); + if reinitialize(repo).is_ok() { + continue; + } } - } - return Err(err.into()); - } - Ok(()) - }) + return Err(err.into()); + } + Ok(()) + }) + } } fn fetch_with_cli( diff --git a/src/cargo/sources/path.rs b/src/cargo/sources/path.rs index c4cff058ba9..37e1e1f0f9d 100644 --- a/src/cargo/sources/path.rs +++ b/src/cargo/sources/path.rs @@ -574,4 +574,8 @@ impl<'cfg> Source for PathSource<'cfg> { fn invalidate_cache(&mut self) { // Path source has no local cache. } + + fn set_quiet(&mut self, _quiet: bool) { + // Path source does not display status + } } diff --git a/src/cargo/sources/registry/http_remote.rs b/src/cargo/sources/registry/http_remote.rs index c3bcadf60bf..7e1f2a58740 100644 --- a/src/cargo/sources/registry/http_remote.rs +++ b/src/cargo/sources/registry/http_remote.rs @@ -7,12 +7,13 @@ use crate::ops::{self}; use crate::sources::registry::download; use crate::sources::registry::MaybeLock; use crate::sources::registry::{LoadResponse, RegistryConfig, RegistryData}; -use crate::util::errors::{CargoResult, HttpNotSuccessful}; -use crate::util::network::Retry; +use crate::util::errors::{CargoResult, HttpNotSuccessful, DEBUG_HEADERS}; +use crate::util::network::retry::{Retry, RetryResult}; +use crate::util::network::sleep::SleepTracker; use crate::util::{auth, Config, Filesystem, IntoUrl, Progress, ProgressStyle}; use anyhow::Context; use cargo_util::paths; -use curl::easy::{HttpVersion, List}; +use curl::easy::{Easy, HttpVersion, List}; use curl::multi::{EasyHandle, Multi}; use log::{debug, trace, warn}; use std::cell::RefCell; @@ -89,10 +90,13 @@ pub struct HttpRegistry<'cfg> { /// Url to get a token for the registry. login_url: Option, + + /// Disables status messages. + quiet: bool, } /// Helper for downloading crates. -pub struct Downloads<'cfg> { +struct Downloads<'cfg> { /// When a download is started, it is added to this map. The key is a /// "token" (see `Download::token`). It is removed once the download is /// finished. @@ -100,6 +104,8 @@ pub struct Downloads<'cfg> { /// Set of paths currently being downloaded. /// This should stay in sync with `pending`. pending_paths: HashSet, + /// Downloads that have failed and are waiting to retry again later. + sleeping: SleepTracker<(Download<'cfg>, Easy)>, /// The final result of each download. results: HashMap>, /// The next ID to use for creating a token (see `Download::token`). @@ -136,6 +142,7 @@ struct Headers { last_modified: Option, etag: Option, www_authenticate: Vec, + others: Vec, } enum StatusCode { @@ -181,6 +188,7 @@ impl<'cfg> HttpRegistry<'cfg> { next: 0, pending: HashMap::new(), pending_paths: HashSet::new(), + sleeping: SleepTracker::new(), results: HashMap::new(), progress: RefCell::new(Some(Progress::with_style( "Fetch", @@ -196,6 +204,7 @@ impl<'cfg> HttpRegistry<'cfg> { registry_config: None, auth_required: false, login_url: None, + quiet: false, }) } @@ -231,9 +240,11 @@ impl<'cfg> HttpRegistry<'cfg> { // let's not flood the server with connections self.multi.set_max_host_connections(2)?; - self.config - .shell() - .status("Updating", self.source_id.display_index())?; + if !self.quiet { + self.config + .shell() + .status("Updating", self.source_id.display_index())?; + } Ok(()) } @@ -259,6 +270,12 @@ impl<'cfg> HttpRegistry<'cfg> { }; for (token, result) in results { let (mut download, handle) = self.downloads.pending.remove(&token).unwrap(); + let was_present = self.downloads.pending_paths.remove(&download.path); + assert!( + was_present, + "expected pending_paths to contain {:?}", + download.path + ); let mut handle = self.multi.remove(handle)?; let data = download.data.take(); let url = self.full_url(&download.path); @@ -271,33 +288,31 @@ impl<'cfg> HttpRegistry<'cfg> { 304 => StatusCode::NotModified, 401 => StatusCode::Unauthorized, 404 | 410 | 451 => StatusCode::NotFound, - code => { - let url = handle.effective_url()?.unwrap_or(&url); - return Err(HttpNotSuccessful { - code, - url: url.to_owned(), - body: data, - } + _ => { + return Err(HttpNotSuccessful::new_from_handle( + &mut handle, + &url, + data, + download.header_map.take().others, + ) .into()); } }; Ok((data, code)) }) { - Ok(Some((data, code))) => Ok(CompletedDownload { + RetryResult::Success((data, code)) => Ok(CompletedDownload { response_code: code, data, header_map: download.header_map.take(), }), - Ok(None) => { - // retry the operation - let handle = self.multi.add(handle)?; - self.downloads.pending.insert(token, (download, handle)); + RetryResult::Err(e) => Err(e), + RetryResult::Retry(sleep) => { + debug!("download retry {:?} for {sleep}ms", download.path); + self.downloads.sleeping.push(sleep, (download, handle)); continue; } - Err(e) => Err(e), }; - assert!(self.downloads.pending_paths.remove(&download.path)); self.downloads.results.insert(download.path, result); self.downloads.downloads_finished += 1; } @@ -389,6 +404,18 @@ impl<'cfg> HttpRegistry<'cfg> { ))), } } + + fn add_sleepers(&mut self) -> CargoResult<()> { + for (dl, handle) in self.downloads.sleeping.to_retry() { + let mut handle = self.multi.add(handle)?; + handle.set_token(dl.token)?; + let is_new = self.downloads.pending_paths.insert(dl.path.to_path_buf()); + assert!(is_new, "path queued for download more than once"); + let previous = self.downloads.pending.insert(dl.token, (dl, handle)); + assert!(previous.is_none(), "dl token queued more than once"); + } + Ok(()) + } } impl<'cfg> RegistryData for HttpRegistry<'cfg> { @@ -438,12 +465,20 @@ impl<'cfg> RegistryData for HttpRegistry<'cfg> { return Poll::Ready(Ok(LoadResponse::NotFound)); } + if self.config.offline() || self.config.cli_unstable().no_index_update { + // Return NotFound in offline mode when the file doesn't exist in the cache. + // If this results in resolution failure, the resolver will suggest + // removing the --offline flag. + return Poll::Ready(Ok(LoadResponse::NotFound)); + } + if let Some(result) = self.downloads.results.remove(path) { let result = result.with_context(|| format!("download of {} failed", path.display()))?; + let is_new = self.fresh.insert(path.to_path_buf()); assert!( - self.fresh.insert(path.to_path_buf()), + is_new, "downloaded the index file `{}` twice", path.display() ); @@ -512,11 +547,14 @@ impl<'cfg> RegistryData for HttpRegistry<'cfg> { code: 401, body: result.data, url: self.full_url(path), + ip: None, + headers: result.header_map.others, } .into()); if self.auth_required { return Poll::Ready(err.context(auth::AuthorizationError { sid: self.source_id.clone(), + default_registry: self.config.default_registry()?, login_url: self.login_url.clone(), reason: auth::AuthorizationErrorReason::TokenRejected, })); @@ -599,10 +637,8 @@ impl<'cfg> RegistryData for HttpRegistry<'cfg> { let token = self.downloads.next; self.downloads.next += 1; debug!("downloading {} as {}", path.display(), token); - assert!( - self.downloads.pending_paths.insert(path.to_path_buf()), - "path queued for download more than once" - ); + let is_new = self.downloads.pending_paths.insert(path.to_path_buf()); + assert!(is_new, "path queued for download more than once"); // Each write should go to self.downloads.pending[&token].data. // Since the write function must be 'static, we access downloads through a thread-local. @@ -632,7 +668,11 @@ impl<'cfg> RegistryData for HttpRegistry<'cfg> { LAST_MODIFIED => header_map.last_modified = Some(value.to_string()), ETAG => header_map.etag = Some(value.to_string()), WWW_AUTHENTICATE => header_map.www_authenticate.push(value.to_string()), - _ => {} + _ => { + if DEBUG_HEADERS.iter().any(|prefix| tag.starts_with(prefix)) { + header_map.others.push(format!("{tag}: {value}")); + } + } } } }); @@ -674,6 +714,11 @@ impl<'cfg> RegistryData for HttpRegistry<'cfg> { self.requested_update = true; } + fn set_quiet(&mut self, quiet: bool) { + self.quiet = quiet; + self.downloads.progress.replace(None); + } + fn download(&mut self, pkg: PackageId, checksum: &str) -> CargoResult { let registry_config = loop { match self.config()? { @@ -712,6 +757,7 @@ impl<'cfg> RegistryData for HttpRegistry<'cfg> { loop { self.handle_completed_downloads()?; + self.add_sleepers()?; let remaining_in_multi = tls::set(&self.downloads, || { self.multi @@ -720,19 +766,25 @@ impl<'cfg> RegistryData for HttpRegistry<'cfg> { })?; trace!("{} transfers remaining", remaining_in_multi); - if remaining_in_multi == 0 { + if remaining_in_multi + self.downloads.sleeping.len() as u32 == 0 { return Ok(()); } - // We have no more replies to provide the caller with, - // so we need to wait until cURL has something new for us. - let timeout = self - .multi - .get_timeout()? - .unwrap_or_else(|| Duration::new(1, 0)); - self.multi - .wait(&mut [], timeout) - .with_context(|| "failed to wait on curl `Multi`")?; + if self.downloads.pending.is_empty() { + let delay = self.downloads.sleeping.time_to_next().unwrap(); + debug!("sleeping main thread for {delay:?}"); + std::thread::sleep(delay); + } else { + // We have no more replies to provide the caller with, + // so we need to wait until cURL has something new for us. + let timeout = self + .multi + .get_timeout()? + .unwrap_or_else(|| Duration::new(1, 0)); + self.multi + .wait(&mut [], timeout) + .with_context(|| "failed to wait on curl `Multi`")?; + } } } } @@ -740,7 +792,7 @@ impl<'cfg> RegistryData for HttpRegistry<'cfg> { impl<'cfg> Downloads<'cfg> { fn tick(&self) -> CargoResult<()> { let mut progress = self.progress.borrow_mut(); - let progress = progress.as_mut().unwrap(); + let Some(progress) = progress.as_mut() else { return Ok(()); }; // Since the sparse protocol discovers dependencies as it goes, // it's not possible to get an accurate progress indication. @@ -761,7 +813,7 @@ impl<'cfg> Downloads<'cfg> { &format!( " {} complete; {} pending", self.downloads_finished, - self.pending.len() + self.pending.len() + self.sleeping.len() ), ) } @@ -773,7 +825,7 @@ mod tls { thread_local!(static PTR: Cell = Cell::new(0)); - pub(crate) fn with(f: impl FnOnce(Option<&Downloads<'_>>) -> R) -> R { + pub(super) fn with(f: impl FnOnce(Option<&Downloads<'_>>) -> R) -> R { let ptr = PTR.with(|p| p.get()); if ptr == 0 { f(None) @@ -784,7 +836,7 @@ mod tls { } } - pub(crate) fn set(dl: &Downloads<'_>, f: impl FnOnce() -> R) -> R { + pub(super) fn set(dl: &Downloads<'_>, f: impl FnOnce() -> R) -> R { struct Reset<'a, T: Copy>(&'a Cell, T); impl<'a, T: Copy> Drop for Reset<'a, T> { diff --git a/src/cargo/sources/registry/index.rs b/src/cargo/sources/registry/index.rs index 633ed74b4d6..a215114347c 100644 --- a/src/cargo/sources/registry/index.rs +++ b/src/cargo/sources/registry/index.rs @@ -66,7 +66,6 @@ //! details like invalidating caches and whatnot which are handled below, but //! hopefully those are more obvious inline in the code itself. -use crate::core::dependency::Dependency; use crate::core::{PackageId, SourceId, Summary}; use crate::sources::registry::{LoadResponse, RegistryData, RegistryPackage, INDEX_V_MAX}; use crate::util::interning::InternedString; @@ -87,14 +86,14 @@ use std::task::{ready, Poll}; /// This loop tries all possible combinations of switching hyphen and underscores to find the /// uncanonicalized one. As all stored inputs have the correct spelling, we start with the spelling /// as-provided. -struct UncanonicalizedIter<'s> { +pub struct UncanonicalizedIter<'s> { input: &'s str, num_hyphen_underscore: u32, hyphen_combination_num: u16, } impl<'s> UncanonicalizedIter<'s> { - fn new(input: &'s str) -> Self { + pub fn new(input: &'s str) -> Self { let num_hyphen_underscore = input.chars().filter(|&c| c == '_' || c == '-').count() as u32; UncanonicalizedIter { input, @@ -267,7 +266,7 @@ impl<'cfg> RegistryIndex<'cfg> { /// Returns the hash listed for a specified `PackageId`. pub fn hash(&mut self, pkg: PackageId, load: &mut dyn RegistryData) -> Poll> { let req = OptVersionReq::exact(pkg.version()); - let summary = self.summaries(pkg.name(), &req, load)?; + let summary = self.summaries(&pkg.name(), &req, load)?; let summary = ready!(summary).next(); Poll::Ready(Ok(summary .ok_or_else(|| internal(format!("no hash listed for {}", pkg)))? @@ -285,7 +284,7 @@ impl<'cfg> RegistryIndex<'cfg> { /// though since this method is called quite a lot on null builds in Cargo. pub fn summaries<'a, 'b>( &'a mut self, - name: InternedString, + name: &str, req: &'b OptVersionReq, load: &mut dyn RegistryData, ) -> Poll + 'b>> @@ -299,6 +298,7 @@ impl<'cfg> RegistryIndex<'cfg> { // has run previously this will parse a Cargo-specific cache file rather // than the registry itself. In effect this is intended to be a quite // cheap operation. + let name = InternedString::new(name); let summaries = ready!(self.load_summaries(name, load)?); // Iterate over our summaries, extract all relevant ones which match our @@ -360,46 +360,18 @@ impl<'cfg> RegistryIndex<'cfg> { .chars() .flat_map(|c| c.to_lowercase()) .collect::(); - let raw_path = make_dep_path(&fs_name, false); - - let mut any_pending = false; - // Attempt to handle misspellings by searching for a chain of related - // names to the original `raw_path` name. Only return summaries - // associated with the first hit, however. The resolver will later - // reject any candidates that have the wrong name, and with this it'll - // along the way produce helpful "did you mean?" suggestions. - for (i, path) in UncanonicalizedIter::new(&raw_path).take(1024).enumerate() { - let summaries = Summaries::parse( - root, - &cache_root, - path.as_ref(), - self.source_id, - load, - self.config, - )?; - if summaries.is_pending() { - if i == 0 { - // If we have not herd back about the name as requested - // then don't ask about other spellings yet. - // This prevents us spamming all the variations in the - // case where we have the correct spelling. - return Poll::Pending; - } - any_pending = true; - } - if let Poll::Ready(Some(summaries)) = summaries { - self.summaries_cache.insert(name, summaries); - return Poll::Ready(Ok(self.summaries_cache.get_mut(&name).unwrap())); - } - } - - if any_pending { - return Poll::Pending; - } - // If nothing was found then this crate doesn't exists, so just use an - // empty `Summaries` list. - self.summaries_cache.insert(name, Summaries::default()); + let path = make_dep_path(&fs_name, false); + let summaries = ready!(Summaries::parse( + root, + &cache_root, + path.as_ref(), + self.source_id, + load, + self.config, + ))? + .unwrap_or_default(); + self.summaries_cache.insert(name, summaries); Poll::Ready(Ok(self.summaries_cache.get_mut(&name).unwrap())) } @@ -410,7 +382,8 @@ impl<'cfg> RegistryIndex<'cfg> { pub fn query_inner( &mut self, - dep: &Dependency, + name: &str, + req: &OptVersionReq, load: &mut dyn RegistryData, yanked_whitelist: &HashSet, f: &mut dyn FnMut(Summary), @@ -426,17 +399,20 @@ impl<'cfg> RegistryIndex<'cfg> { // then cargo will fail to download and an error message // indicating that the required dependency is unavailable while // offline will be displayed. - if ready!(self.query_inner_with_online(dep, load, yanked_whitelist, f, false)?) > 0 { + if ready!(self.query_inner_with_online(name, req, load, yanked_whitelist, f, false)?) + > 0 + { return Poll::Ready(Ok(())); } } - self.query_inner_with_online(dep, load, yanked_whitelist, f, true) + self.query_inner_with_online(name, req, load, yanked_whitelist, f, true) .map_ok(|_| ()) } fn query_inner_with_online( &mut self, - dep: &Dependency, + name: &str, + req: &OptVersionReq, load: &mut dyn RegistryData, yanked_whitelist: &HashSet, f: &mut dyn FnMut(Summary), @@ -444,7 +420,7 @@ impl<'cfg> RegistryIndex<'cfg> { ) -> Poll> { let source_id = self.source_id; - let summaries = ready!(self.summaries(dep.package_name(), dep.version_req(), load))?; + let summaries = ready!(self.summaries(name, req, load))?; let summaries = summaries // First filter summaries for `--offline`. If we're online then @@ -469,7 +445,6 @@ impl<'cfg> RegistryIndex<'cfg> { // `=o->` where `` is the name of a crate on // this source, `` is the version installed and ` is the // version requested (argument to `--precise`). - let name = dep.package_name().as_str(); let precise = match source_id.precise() { Some(p) if p.starts_with(name) && p[name.len()..].starts_with('=') => { let mut vers = p[name.len() + 1..].splitn(2, "->"); @@ -481,7 +456,7 @@ impl<'cfg> RegistryIndex<'cfg> { }; let summaries = summaries.filter(|s| match &precise { Some((current, requested)) => { - if dep.version_req().matches(current) { + if req.matches(current) { // Unfortunately crates.io allows versions to differ only // by build metadata. This shouldn't be allowed, but since // it is, this will honor it if requested. However, if not @@ -521,7 +496,7 @@ impl<'cfg> RegistryIndex<'cfg> { ) -> Poll> { let req = OptVersionReq::exact(pkg.version()); let found = self - .summaries(pkg.name(), &req, load) + .summaries(&pkg.name(), &req, load) .map_ok(|mut p| p.any(|summary| summary.yanked)); found } diff --git a/src/cargo/sources/registry/local.rs b/src/cargo/sources/registry/local.rs index a4b57a91e7a..89419191f97 100644 --- a/src/cargo/sources/registry/local.rs +++ b/src/cargo/sources/registry/local.rs @@ -18,6 +18,7 @@ pub struct LocalRegistry<'cfg> { src_path: Filesystem, config: &'cfg Config, updated: bool, + quiet: bool, } impl<'cfg> LocalRegistry<'cfg> { @@ -28,6 +29,7 @@ impl<'cfg> LocalRegistry<'cfg> { root: Filesystem::new(root.to_path_buf()), config, updated: false, + quiet: false, } } } @@ -104,6 +106,10 @@ impl<'cfg> RegistryData for LocalRegistry<'cfg> { // Local registry has no cache - just reads from disk. } + fn set_quiet(&mut self, _quiet: bool) { + self.quiet = true; + } + fn is_updated(&self) -> bool { self.updated } @@ -124,7 +130,9 @@ impl<'cfg> RegistryData for LocalRegistry<'cfg> { return Ok(MaybeLock::Ready(crate_file)); } - self.config.shell().status("Unpacking", pkg)?; + if !self.quiet { + self.config.shell().status("Unpacking", pkg)?; + } // We don't actually need to download anything per-se, we just need to // verify the checksum matches the .crate file itself. diff --git a/src/cargo/sources/registry/mod.rs b/src/cargo/sources/registry/mod.rs index 930a42f3678..aa3f5dc5ff4 100644 --- a/src/cargo/sources/registry/mod.rs +++ b/src/cargo/sources/registry/mod.rs @@ -164,7 +164,7 @@ use std::collections::HashSet; use std::fs::{File, OpenOptions}; use std::io::{self, Write}; use std::path::{Path, PathBuf}; -use std::task::Poll; +use std::task::{ready, Poll}; use anyhow::Context as _; use cargo_util::paths::{self, exclude_from_backups_and_indexing}; @@ -474,6 +474,9 @@ pub trait RegistryData { /// Invalidates locally cached data. fn invalidate_cache(&mut self); + /// If quiet, the source should not display any progress or status messages. + fn set_quiet(&mut self, quiet: bool); + /// Is the local cached data up-to-date? fn is_updated(&self) -> bool; @@ -753,7 +756,7 @@ impl<'cfg> RegistrySource<'cfg> { let req = OptVersionReq::exact(package.version()); let summary_with_cksum = self .index - .summaries(package.name(), &req, &mut *self.ops)? + .summaries(&package.name(), &req, &mut *self.ops)? .expect("a downloaded dep now pending!?") .map(|s| s.summary.clone()) .next() @@ -783,36 +786,73 @@ impl<'cfg> Source for RegistrySource<'cfg> { { debug!("attempting query without update"); let mut called = false; - let pend = - self.index - .query_inner(dep, &mut *self.ops, &self.yanked_whitelist, &mut |s| { - if dep.matches(&s) { - called = true; - f(s); - } - })?; - if pend.is_pending() { - return Poll::Pending; - } + ready!(self.index.query_inner( + &dep.package_name(), + dep.version_req(), + &mut *self.ops, + &self.yanked_whitelist, + &mut |s| { + if dep.matches(&s) { + called = true; + f(s); + } + }, + ))?; if called { - return Poll::Ready(Ok(())); + Poll::Ready(Ok(())) } else { debug!("falling back to an update"); self.invalidate_cache(); - return Poll::Pending; + Poll::Pending } - } - - self.index - .query_inner(dep, &mut *self.ops, &self.yanked_whitelist, &mut |s| { - let matched = match kind { - QueryKind::Exact => dep.matches(&s), - QueryKind::Fuzzy => true, - }; - if matched { - f(s); + } else { + let mut called = false; + ready!(self.index.query_inner( + &dep.package_name(), + dep.version_req(), + &mut *self.ops, + &self.yanked_whitelist, + &mut |s| { + let matched = match kind { + QueryKind::Exact => dep.matches(&s), + QueryKind::Fuzzy => true, + }; + if matched { + f(s); + called = true; + } } - }) + ))?; + if called { + return Poll::Ready(Ok(())); + } + let mut any_pending = false; + if kind == QueryKind::Fuzzy { + // Attempt to handle misspellings by searching for a chain of related + // names to the original name. The resolver will later + // reject any candidates that have the wrong name, and with this it'll + // along the way produce helpful "did you mean?" suggestions. + for name_permutation in + index::UncanonicalizedIter::new(&dep.package_name()).take(1024) + { + any_pending |= self + .index + .query_inner( + &name_permutation, + dep.version_req(), + &mut *self.ops, + &self.yanked_whitelist, + f, + )? + .is_pending(); + } + } + if any_pending { + Poll::Pending + } else { + Poll::Ready(Ok(())) + } + } } fn supports_checksums(&self) -> bool { @@ -832,6 +872,10 @@ impl<'cfg> Source for RegistrySource<'cfg> { self.ops.invalidate_cache(); } + fn set_quiet(&mut self, quiet: bool) { + self.ops.set_quiet(quiet); + } + fn download(&mut self, package: PackageId) -> CargoResult { let hash = loop { match self.index.hash(package, &mut *self.ops)? { diff --git a/src/cargo/sources/registry/remote.rs b/src/cargo/sources/registry/remote.rs index aa0ec90233e..3e502914472 100644 --- a/src/cargo/sources/registry/remote.rs +++ b/src/cargo/sources/registry/remote.rs @@ -32,6 +32,7 @@ pub struct RemoteRegistry<'cfg> { head: Cell>, current_sha: Cell>, needs_update: bool, // Does this registry need to be updated? + quiet: bool, } impl<'cfg> RemoteRegistry<'cfg> { @@ -48,6 +49,7 @@ impl<'cfg> RemoteRegistry<'cfg> { head: Cell::new(None), current_sha: Cell::new(None), needs_update: false, + quiet: false, } } @@ -292,9 +294,11 @@ impl<'cfg> RegistryData for RemoteRegistry<'cfg> { *self.tree.borrow_mut() = None; self.current_sha.set(None); let path = self.config.assert_package_cache_locked(&self.index_path); - self.config - .shell() - .status("Updating", self.source_id.display_index())?; + if !self.quiet { + self.config + .shell() + .status("Updating", self.source_id.display_index())?; + } // Fetch the latest version of our `index_git_ref` into the index // checkout. @@ -315,6 +319,10 @@ impl<'cfg> RegistryData for RemoteRegistry<'cfg> { self.needs_update = true; } + fn set_quiet(&mut self, quiet: bool) { + self.quiet = quiet; + } + fn is_updated(&self) -> bool { self.is_updated() } diff --git a/src/cargo/sources/replaced.rs b/src/cargo/sources/replaced.rs index a2147703388..13191d2234f 100644 --- a/src/cargo/sources/replaced.rs +++ b/src/cargo/sources/replaced.rs @@ -67,6 +67,10 @@ impl<'cfg> Source for ReplacedSource<'cfg> { self.inner.invalidate_cache() } + fn set_quiet(&mut self, quiet: bool) { + self.inner.set_quiet(quiet); + } + fn download(&mut self, id: PackageId) -> CargoResult { let id = id.with_source_id(self.replace_with); let pkg = self diff --git a/src/cargo/util/auth.rs b/src/cargo/util/auth.rs index f7d0fc8c4a2..f19acaebe08 100644 --- a/src/cargo/util/auth.rs +++ b/src/cargo/util/auth.rs @@ -133,6 +133,8 @@ pub fn registry_credential_config( secret_key_subject: Option, #[serde(rename = "default")] _default: Option, + #[serde(rename = "protocol")] + _protocol: Option, } log::trace!("loading credential config for {}", sid); @@ -345,6 +347,8 @@ impl fmt::Display for AuthorizationErrorReason { pub struct AuthorizationError { /// Url that was attempted pub sid: SourceId, + /// The `registry.default` config value. + pub default_registry: Option, /// Url where the user could log in. pub login_url: Option, /// Specific reason indicating what failed @@ -354,9 +358,14 @@ impl Error for AuthorizationError {} impl fmt::Display for AuthorizationError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.sid.is_crates_io() { + let args = if self.default_registry.is_some() { + " --registry crates-io" + } else { + "" + }; write!( f, - "{}, please run `cargo login`\nor use environment variable CARGO_REGISTRY_TOKEN", + "{}, please run `cargo login{args}`\nor use environment variable CARGO_REGISTRY_TOKEN", self.reason ) } else if let Some(name) = self.sid.alt_registry_key() { @@ -419,6 +428,7 @@ pub fn auth_token( Some(token) => Ok(token.expose()), None => Err(AuthorizationError { sid: sid.clone(), + default_registry: config.default_registry()?, login_url: login_url.cloned(), reason: AuthorizationErrorReason::TokenMissing, } diff --git a/src/cargo/util/config/de.rs b/src/cargo/util/config/de.rs index ab4dd93b36a..a9147ab0306 100644 --- a/src/cargo/util/config/de.rs +++ b/src/cargo/util/config/de.rs @@ -424,7 +424,7 @@ impl<'config> ValueDeserializer<'config> { let definition = { let env = de.key.as_env_key(); let env_def = Definition::Environment(env.to_string()); - match (de.config.env_has_key(env), de.config.get_cv(&de.key)?) { + match (de.config.env.contains_key(env), de.config.get_cv(&de.key)?) { (true, Some(cv)) => { // Both, pick highest priority. if env_def.is_higher_priority(cv.definition()) { diff --git a/src/cargo/util/config/environment.rs b/src/cargo/util/config/environment.rs new file mode 100644 index 00000000000..0172c88c07f --- /dev/null +++ b/src/cargo/util/config/environment.rs @@ -0,0 +1,189 @@ +//! Encapsulates snapshotting of environment variables. + +use std::collections::HashMap; +use std::ffi::{OsStr, OsString}; + +use crate::util::errors::CargoResult; +use anyhow::{anyhow, bail}; + +/// Generate `case_insensitive_env` and `normalized_env` from the `env`. +fn make_case_insensitive_and_normalized_env( + env: &HashMap, +) -> (HashMap, HashMap) { + let case_insensitive_env: HashMap<_, _> = env + .keys() + .filter_map(|k| k.to_str()) + .map(|k| (k.to_uppercase(), k.to_owned())) + .collect(); + let normalized_env = env + .iter() + // Only keep entries where both the key and value are valid UTF-8, + // since the config env vars only support UTF-8 keys and values. + // Otherwise, the normalized map warning could incorrectly warn about entries that can't be + // read by the config system. + // Please see the docs for `Env` for more context. + .filter_map(|(k, v)| Some((k.to_str()?, v.to_str()?))) + .map(|(k, _)| (k.to_uppercase().replace("-", "_"), k.to_owned())) + .collect(); + (case_insensitive_env, normalized_env) +} + +/// A snapshot of the environment variables available to [`super::Config`]. +/// +/// Currently, the [`Config`](super::Config) supports lookup of environment variables +/// through two different means: +/// +/// - [`Config::get_env`](super::Config::get_env) +/// and [`Config::get_env_os`](super::Config::get_env_os) +/// for process environment variables (similar to [`std::env::var`] and [`std::env::var_os`]), +/// - Typed Config Value API via [`Config::get`](super::Config::get). +/// This is only available for `CARGO_` prefixed environment keys. +/// +/// This type contains the env var snapshot and helper methods for both APIs. +#[derive(Debug)] +pub struct Env { + /// A snapshot of the process's environment variables. + env: HashMap, + /// Used in the typed Config value API for warning messages when config keys are + /// given in the wrong format. + /// + /// Maps from "normalized" (upper case and with "-" replaced by "_") env keys + /// to the actual keys in the environment. + /// The normalized format is the one expected by Cargo. + /// + /// This only holds env keys that are valid UTF-8, since [`super::ConfigKey`] only supports UTF-8 keys. + /// In addition, this only holds env keys whose value in the environment is also valid UTF-8, + /// since the typed Config value API only supports UTF-8 values. + normalized_env: HashMap, + /// Used to implement `get_env` and `get_env_os` on Windows, where env keys are case-insensitive. + /// + /// Maps from uppercased env keys to the actual key in the environment. + /// For example, this might hold a pair `("PATH", "Path")`. + /// Currently only supports UTF-8 keys and values. + case_insensitive_env: HashMap, +} + +impl Env { + /// Create a new `Env` from process's environment variables. + pub fn new() -> Self { + // ALLOWED: This is the only permissible usage of `std::env::vars{_os}` + // within cargo. If you do need access to individual variables without + // interacting with `Config` system, please use `std::env::var{_os}` + // and justify the validity of the usage. + #[allow(clippy::disallowed_methods)] + let env: HashMap<_, _> = std::env::vars_os().collect(); + let (case_insensitive_env, normalized_env) = make_case_insensitive_and_normalized_env(&env); + Self { + env, + case_insensitive_env, + normalized_env, + } + } + + /// Set the env directly from a `HashMap`. + /// This should be used for debugging purposes only. + pub(super) fn from_map(env: HashMap) -> Self { + let env = env.into_iter().map(|(k, v)| (k.into(), v.into())).collect(); + let (case_insensitive_env, normalized_env) = make_case_insensitive_and_normalized_env(&env); + Self { + env, + case_insensitive_env, + normalized_env, + } + } + + /// Returns all environment variables as an iterator, + /// keeping only entries where both the key and value are valid UTF-8. + pub fn iter_str(&self) -> impl Iterator { + self.env + .iter() + .filter_map(|(k, v)| Some((k.to_str()?, v.to_str()?))) + } + + /// Returns all environment variable keys, filtering out keys that are not valid UTF-8. + pub fn keys_str(&self) -> impl Iterator { + self.env.keys().filter_map(|k| k.to_str()) + } + + /// Get the value of environment variable `key` through the `Config` snapshot. + /// + /// This can be used similarly to `std::env::var_os`. + /// On Windows, we check for case mismatch since environment keys are case-insensitive. + pub fn get_env_os(&self, key: impl AsRef) -> Option { + match self.env.get(key.as_ref()) { + Some(s) => Some(s.clone()), + None => { + if cfg!(windows) { + self.get_env_case_insensitive(key).cloned() + } else { + None + } + } + } + } + + /// Get the value of environment variable `key` through the `self.env` snapshot. + /// + /// This can be used similarly to `std::env::var`. + /// On Windows, we check for case mismatch since environment keys are case-insensitive. + pub fn get_env(&self, key: impl AsRef) -> CargoResult { + let key = key.as_ref(); + let s = self + .get_env_os(key) + .ok_or_else(|| anyhow!("{key:?} could not be found in the environment snapshot"))?; + + match s.to_str() { + Some(s) => Ok(s.to_owned()), + None => bail!("environment variable value is not valid unicode: {s:?}"), + } + } + + /// Performs a case-insensitive lookup of `key` in the environment. + /// + /// This is relevant on Windows, where environment variables are case-insensitive. + /// Note that this only works on keys that are valid UTF-8 and it uses Unicode uppercase, + /// which may differ from the OS's notion of uppercase. + fn get_env_case_insensitive(&self, key: impl AsRef) -> Option<&OsString> { + let upper_case_key = key.as_ref().to_str()?.to_uppercase(); + let env_key: &OsStr = self.case_insensitive_env.get(&upper_case_key)?.as_ref(); + self.env.get(env_key) + } + + /// Get the value of environment variable `key` as a `&str`. + /// Returns `None` if `key` is not in `self.env` or if the value is not valid UTF-8. + /// + /// This is intended for use in private methods of `Config`, + /// and does not check for env key case mismatch. + /// + /// This is case-sensitive on Windows (even though environment keys on Windows are usually + /// case-insensitive) due to an unintended regression in 1.28 (via #5552). + /// This should only affect keys used for cargo's config-system env variables (`CARGO_` + /// prefixed ones), which are currently all uppercase. + /// We may want to consider rectifying it if users report issues. + /// One thing that adds a wrinkle here is the unstable advanced-env option that *requires* + /// case-sensitive keys. + /// + /// Do not use this for any other purposes. + /// Use [`Env::get_env_os`] or [`Env::get_env`] instead, which properly handle case + /// insensitivity on Windows. + pub(super) fn get_str(&self, key: impl AsRef) -> Option<&str> { + self.env.get(key.as_ref()).and_then(|s| s.to_str()) + } + + /// Check if the environment contains `key`. + /// + /// This is intended for use in private methods of `Config`, + /// and does not check for env key case mismatch. + /// See the docstring of [`Env::get_str`] for more context. + pub(super) fn contains_key(&self, key: impl AsRef) -> bool { + self.env.contains_key(key.as_ref()) + } + + /// Looks up a normalized `key` in the `normalized_env`. + /// Returns the corresponding (non-normalized) env key if it exists, else `None`. + /// + /// This is used by [`super::Config::check_environment_key_case_mismatch`]. + pub(super) fn get_normalized(&self, key: &str) -> Option<&str> { + self.normalized_env.get(key).map(|s| s.as_ref()) + } +} diff --git a/src/cargo/util/config/mod.rs b/src/cargo/util/config/mod.rs index b5c781efddd..dad7e9c727b 100644 --- a/src/cargo/util/config/mod.rs +++ b/src/cargo/util/config/mod.rs @@ -72,9 +72,9 @@ use crate::core::{features, CliUnstable, Shell, SourceId, Workspace, WorkspaceRo use crate::ops::{self, RegistryCredentialConfig}; use crate::util::auth::Secret; use crate::util::errors::CargoResult; -use crate::util::validate_package_name; use crate::util::CanonicalUrl; use crate::util::{internal, toml as cargo_toml}; +use crate::util::{try_canonicalize, validate_package_name}; use crate::util::{FileLock, Filesystem, IntoUrl, IntoUrlWithBase, Rustc}; use anyhow::{anyhow, bail, format_err, Context as _}; use cargo_util::paths; @@ -100,6 +100,9 @@ pub use path::{ConfigRelativePath, PathAndArgs}; mod target; pub use target::{TargetCfgConfig, TargetConfig}; +mod environment; +use environment::Env; + // Helper macro for creating typed access methods. macro_rules! get_value_typed { ($name:ident, $ty:ty, $variant:ident, $expected:expr) => { @@ -199,10 +202,8 @@ pub struct Config { creation_time: Instant, /// Target Directory via resolved Cli parameter target_dir: Option, - /// Environment variables, separated to assist testing. - env: HashMap, - /// Environment variables, converted to uppercase to check for case mismatch - upper_case_env: HashMap, + /// Environment variable snapshot. + env: Env, /// Tracks which sources have been updated to avoid multiple updates. updated_sources: LazyCell>>, /// Cache of credentials from configuration or credential providers. @@ -260,16 +261,10 @@ impl Config { } }); - let env: HashMap<_, _> = env::vars_os().collect(); - - let upper_case_env = env - .iter() - .filter_map(|(k, _)| k.to_str()) // Only keep valid UTF-8 - .map(|k| (k.to_uppercase().replace("-", "_"), k.to_owned())) - .collect(); + let env = Env::new(); - let cache_key: &OsStr = "CARGO_CACHE_RUSTC_INFO".as_ref(); - let cache_rustc_info = match env.get(cache_key) { + let cache_key = "CARGO_CACHE_RUSTC_INFO"; + let cache_rustc_info = match env.get_env_os(cache_key) { Some(cache) => cache != "0", _ => true, }; @@ -303,7 +298,6 @@ impl Config { creation_time: Instant::now(), target_dir: None, env, - upper_case_env, updated_sources: LazyCell::new(), credential_cache: LazyCell::new(), package_cache_lock: RefCell::new(None), @@ -439,11 +433,11 @@ impl Config { // commands that use Cargo as a library to inherit (via `cargo `) // or set (by setting `$CARGO`) a correct path to `cargo` when the current exe // is not actually cargo (e.g., `cargo-*` binaries, Valgrind, `ld.so`, etc.). - let exe = self - .get_env_os(crate::CARGO_ENV) - .map(PathBuf::from) - .ok_or_else(|| anyhow!("$CARGO not set"))? - .canonicalize()?; + let exe = try_canonicalize( + self.get_env_os(crate::CARGO_ENV) + .map(PathBuf::from) + .ok_or_else(|| anyhow!("$CARGO not set"))?, + )?; Ok(exe) }; @@ -452,7 +446,7 @@ impl Config { // The method varies per operating system and might fail; in particular, // it depends on `/proc` being mounted on Linux, and some environments // (like containers or chroots) may not have that available. - let exe = env::current_exe()?.canonicalize()?; + let exe = try_canonicalize(env::current_exe()?)?; Ok(exe) } @@ -658,7 +652,7 @@ impl Config { // Root table can't have env value. return Ok(cv); } - let env = self.get_env_str(key.as_env_key()); + let env = self.env.get_str(key.as_env_key()); let env_def = Definition::Environment(key.as_env_key().to_string()); let use_env = match (&cv, env) { // Lists are always merged. @@ -729,20 +723,18 @@ impl Config { /// Helper primarily for testing. pub fn set_env(&mut self, env: HashMap) { - self.env = env.into_iter().map(|(k, v)| (k.into(), v.into())).collect(); + self.env = Env::from_map(env); } - /// Returns all environment variables as an iterator, filtering out entries - /// that are not valid UTF-8. + /// Returns all environment variables as an iterator, + /// keeping only entries where both the key and value are valid UTF-8. pub(crate) fn env(&self) -> impl Iterator { - self.env - .iter() - .filter_map(|(k, v)| Some((k.to_str()?, v.to_str()?))) + self.env.iter_str() } - /// Returns all environment variable keys, filtering out entries that are not valid UTF-8. + /// Returns all environment variable keys, filtering out keys that are not valid UTF-8. fn env_keys(&self) -> impl Iterator { - self.env.iter().filter_map(|(k, _)| k.to_str()) + self.env.keys_str() } fn get_config_env(&self, key: &ConfigKey) -> Result, ConfigError> @@ -750,7 +742,7 @@ impl Config { T: FromStr, ::Err: fmt::Display, { - match self.get_env_str(key.as_env_key()) { + match self.env.get_str(key.as_env_key()) { Some(value) => { let definition = Definition::Environment(key.as_env_key().to_string()); Ok(Some(Value { @@ -771,39 +763,21 @@ impl Config { /// /// This can be used similarly to `std::env::var`. pub fn get_env(&self, key: impl AsRef) -> CargoResult { - let key = key.as_ref(); - let s = match self.env.get(key) { - Some(s) => s, - None => bail!("{key:?} could not be found in the environment snapshot",), - }; - match s.to_str() { - Some(s) => Ok(s.to_owned()), - None => bail!("environment variable value is not valid unicode: {s:?}"), - } + self.env.get_env(key) } /// Get the value of environment variable `key` through the `Config` snapshot. /// /// This can be used similarly to `std::env::var_os`. pub fn get_env_os(&self, key: impl AsRef) -> Option { - self.env.get(key.as_ref()).cloned() - } - - /// Get the value of environment variable `key`. - /// Returns `None` if `key` is not in `self.env` or if the value is not valid UTF-8. - fn get_env_str(&self, key: impl AsRef) -> Option<&str> { - self.env.get(key.as_ref()).and_then(|s| s.to_str()) - } - - fn env_has_key(&self, key: impl AsRef) -> bool { - self.env.contains_key(key.as_ref()) + self.env.get_env_os(key) } /// Check if the [`Config`] contains a given [`ConfigKey`]. /// /// See `ConfigMapAccess` for a description of `env_prefix_ok`. fn has_key(&self, key: &ConfigKey, env_prefix_ok: bool) -> CargoResult { - if self.env_has_key(key.as_env_key()) { + if self.env.contains_key(key.as_env_key()) { return Ok(true); } if env_prefix_ok { @@ -821,7 +795,7 @@ impl Config { } fn check_environment_key_case_mismatch(&self, key: &ConfigKey) { - if let Some(env_key) = self.upper_case_env.get(key.as_env_key()) { + if let Some(env_key) = self.env.get_normalized(key.as_env_key()) { let _ = self.shell().warn(format!( "Environment variables are expected to use uppercase letters and underscores, \ the variable `{}` will be ignored and have no effect", @@ -920,7 +894,7 @@ impl Config { key: &ConfigKey, output: &mut Vec<(String, Definition)>, ) -> CargoResult<()> { - let env_val = match self.get_env_str(key.as_env_key()) { + let env_val = match self.env.get_str(key.as_env_key()) { Some(v) => v, None => { self.check_environment_key_case_mismatch(key); diff --git a/src/cargo/util/errors.rs b/src/cargo/util/errors.rs index fb63f6ae2dd..5c7eebcdb71 100644 --- a/src/cargo/util/errors.rs +++ b/src/cargo/util/errors.rs @@ -1,31 +1,100 @@ #![allow(unknown_lints)] use anyhow::Error; -use std::fmt; +use curl::easy::Easy; +use std::fmt::{self, Write}; use std::path::PathBuf; use super::truncate_with_ellipsis; pub type CargoResult = anyhow::Result; +/// These are headers that are included in error messages to help with +/// diagnosing issues. +pub const DEBUG_HEADERS: &[&str] = &[ + // This is the unique ID that identifies the request in CloudFront which + // can be used for looking at the AWS logs. + "x-amz-cf-id", + // This is the CloudFront POP (Point of Presence) that identifies the + // region where the request was routed. This can help identify if an issue + // is region-specific. + "x-amz-cf-pop", + // The unique token used for troubleshooting S3 requests via AWS logs or support. + "x-amz-request-id", + // Another token used in conjunction with x-amz-request-id. + "x-amz-id-2", + // Whether or not there was a cache hit or miss (both CloudFront and Fastly). + "x-cache", + // The cache server that processed the request (Fastly). + "x-served-by", +]; + #[derive(Debug)] pub struct HttpNotSuccessful { pub code: u32, pub url: String, + pub ip: Option, pub body: Vec, + pub headers: Vec, } -impl fmt::Display for HttpNotSuccessful { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +impl HttpNotSuccessful { + pub fn new_from_handle( + handle: &mut Easy, + initial_url: &str, + body: Vec, + headers: Vec, + ) -> HttpNotSuccessful { + let ip = handle.primary_ip().ok().flatten().map(|s| s.to_string()); + let url = handle + .effective_url() + .ok() + .flatten() + .unwrap_or(initial_url) + .to_string(); + HttpNotSuccessful { + code: handle.response_code().unwrap_or(0), + url, + ip, + body, + headers, + } + } + + /// Renders the error in a compact form. + pub fn display_short(&self) -> String { + self.render(false) + } + + fn render(&self, show_headers: bool) -> String { + let mut result = String::new(); let body = std::str::from_utf8(&self.body) .map(|s| truncate_with_ellipsis(s, 512)) .unwrap_or_else(|_| format!("[{} non-utf8 bytes]", self.body.len())); write!( - f, - "failed to get successful HTTP response from `{}`, got {}\nbody:\n{body}", - self.url, self.code + result, + "failed to get successful HTTP response from `{}`", + self.url ) + .unwrap(); + if let Some(ip) = &self.ip { + write!(result, " ({ip})").unwrap(); + } + write!(result, ", got {}\n", self.code).unwrap(); + if show_headers { + if !self.headers.is_empty() { + write!(result, "debug headers:\n{}\n", self.headers.join("\n")).unwrap(); + } + } + write!(result, "body:\n{body}").unwrap(); + result + } +} + +impl fmt::Display for HttpNotSuccessful { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.render(true)) } } diff --git a/src/cargo/util/job.rs b/src/cargo/util/job.rs index 9ff9b53042d..f2bcf94a26b 100644 --- a/src/cargo/util/job.rs +++ b/src/cargo/util/job.rs @@ -32,6 +32,9 @@ mod imp { // when-cargo-is-killed-subprocesses-are-also-killed, but that requires // one cargo spawned to become its own session leader, so we do that // here. + // + // ALLOWED: For testing cargo itself only. + #[allow(clippy::disallowed_methods)] if env::var("__CARGO_TEST_SETSID_PLEASE_DONT_USE_ELSEWHERE").is_ok() { libc::setsid(); } diff --git a/src/cargo/util/mod.rs b/src/cargo/util/mod.rs index ccd6d59a4d1..12b1520181f 100644 --- a/src/cargo/util/mod.rs +++ b/src/cargo/util/mod.rs @@ -1,4 +1,5 @@ use std::fmt; +use std::path::{Path, PathBuf}; use std::time::Duration; pub use self::canonical_url::CanonicalUrl; @@ -133,6 +134,79 @@ pub fn truncate_with_ellipsis(s: &str, max_width: usize) -> String { prefix } +#[cfg(not(windows))] +#[inline] +pub fn try_canonicalize>(path: P) -> std::io::Result { + std::fs::canonicalize(&path) +} + +#[cfg(windows)] +#[inline] +pub fn try_canonicalize>(path: P) -> std::io::Result { + use std::ffi::OsString; + use std::io::Error; + use std::os::windows::ffi::{OsStrExt, OsStringExt}; + use std::{io::ErrorKind, ptr}; + use windows_sys::Win32::Foundation::{GetLastError, SetLastError}; + use windows_sys::Win32::Storage::FileSystem::GetFullPathNameW; + + // On Windows `canonicalize` may fail, so we fall back to getting an absolute path. + std::fs::canonicalize(&path).or_else(|_| { + // Return an error if a file does not exist for better compatiblity with `canonicalize` + if !path.as_ref().try_exists()? { + return Err(Error::new(ErrorKind::NotFound, "the path was not found")); + } + + // This code is based on the unstable `std::path::absolute` and could be replaced with it + // if it's stabilized. + + let path = path.as_ref().as_os_str(); + let mut path_u16 = Vec::with_capacity(path.len() + 1); + path_u16.extend(path.encode_wide()); + if path_u16.iter().find(|c| **c == 0).is_some() { + return Err(Error::new( + ErrorKind::InvalidInput, + "strings passed to WinAPI cannot contain NULs", + )); + } + path_u16.push(0); + + loop { + unsafe { + SetLastError(0); + let len = + GetFullPathNameW(path_u16.as_ptr(), 0, &mut [] as *mut u16, ptr::null_mut()); + if len == 0 { + let error = GetLastError(); + if error != 0 { + return Err(Error::from_raw_os_error(error as i32)); + } + } + let mut result = vec![0u16; len as usize]; + + let write_len = GetFullPathNameW( + path_u16.as_ptr(), + result.len().try_into().unwrap(), + result.as_mut_ptr().cast::(), + ptr::null_mut(), + ); + if write_len == 0 { + let error = GetLastError(); + if error != 0 { + return Err(Error::from_raw_os_error(error as i32)); + } + } + + if write_len <= len { + return Ok(PathBuf::from(OsString::from_wide( + &result[0..(write_len as usize)], + ))); + } + } + } + }) +} + #[cfg(test)] mod test { use super::*; diff --git a/src/cargo/util/network/mod.rs b/src/cargo/util/network/mod.rs new file mode 100644 index 00000000000..60a380343b7 --- /dev/null +++ b/src/cargo/util/network/mod.rs @@ -0,0 +1,38 @@ +//! Utilities for networking. + +use std::task::Poll; + +pub mod retry; +pub mod sleep; + +pub trait PollExt { + fn expect(self, msg: &str) -> T; +} + +impl PollExt for Poll { + #[track_caller] + fn expect(self, msg: &str) -> T { + match self { + Poll::Ready(val) => val, + Poll::Pending => panic!("{}", msg), + } + } +} + +// When dynamically linked against libcurl, we want to ignore some failures +// when using old versions that don't support certain features. +#[macro_export] +macro_rules! try_old_curl { + ($e:expr, $msg:expr) => { + let result = $e; + if cfg!(target_os = "macos") { + if let Err(e) = result { + warn!("ignoring libcurl {} error: {}", $msg, e); + } + } else { + result.with_context(|| { + anyhow::format_err!("failed to enable {}, is curl not built right?", $msg) + })?; + } + }; +} diff --git a/src/cargo/util/network.rs b/src/cargo/util/network/retry.rs similarity index 50% rename from src/cargo/util/network.rs rename to src/cargo/util/network/retry.rs index 4b27160b009..42c38ab9f80 100644 --- a/src/cargo/util/network.rs +++ b/src/cargo/util/network/retry.rs @@ -1,50 +1,74 @@ -use anyhow::Error; +//! Utilities for retrying a network operation. -use crate::util::errors::{CargoResult, HttpNotSuccessful}; -use crate::util::Config; -use std::task::Poll; +use crate::util::errors::HttpNotSuccessful; +use crate::{CargoResult, Config}; +use anyhow::Error; +use rand::Rng; +use std::cmp::min; +use std::time::Duration; -pub trait PollExt { - fn expect(self, msg: &str) -> T; +pub struct Retry<'a> { + config: &'a Config, + retries: u64, + max_retries: u64, } -impl PollExt for Poll { - #[track_caller] - fn expect(self, msg: &str) -> T { - match self { - Poll::Ready(val) => val, - Poll::Pending => panic!("{}", msg), - } - } +pub enum RetryResult { + Success(T), + Err(anyhow::Error), + Retry(u64), } -pub struct Retry<'a> { - config: &'a Config, - remaining: u32, -} +/// Maximum amount of time a single retry can be delayed (milliseconds). +const MAX_RETRY_SLEEP_MS: u64 = 10 * 1000; +/// The minimum initial amount of time a retry will be delayed (milliseconds). +/// +/// The actual amount of time will be a random value above this. +const INITIAL_RETRY_SLEEP_BASE_MS: u64 = 500; +/// The maximum amount of additional time the initial retry will take (milliseconds). +/// +/// The initial delay will be [`INITIAL_RETRY_SLEEP_BASE_MS`] plus a random range +/// from 0 to this value. +const INITIAL_RETRY_JITTER_MS: u64 = 1000; impl<'a> Retry<'a> { pub fn new(config: &'a Config) -> CargoResult> { Ok(Retry { config, - remaining: config.net_config()?.retry.unwrap_or(2), + retries: 0, + max_retries: config.net_config()?.retry.unwrap_or(3) as u64, }) } /// Returns `Ok(None)` for operations that should be re-tried. - pub fn r#try(&mut self, f: impl FnOnce() -> CargoResult) -> CargoResult> { + pub fn r#try(&mut self, f: impl FnOnce() -> CargoResult) -> RetryResult { match f() { - Err(ref e) if maybe_spurious(e) && self.remaining > 0 => { + Err(ref e) if maybe_spurious(e) && self.retries < self.max_retries => { + let err_msg = e + .downcast_ref::() + .map(|http_err| http_err.display_short()) + .unwrap_or_else(|| e.root_cause().to_string()); let msg = format!( - "spurious network error ({} tries remaining): {}", - self.remaining, - e.root_cause(), + "spurious network error ({} tries remaining): {err_msg}", + self.max_retries - self.retries, ); - self.config.shell().warn(msg)?; - self.remaining -= 1; - Ok(None) + if let Err(e) = self.config.shell().warn(msg) { + return RetryResult::Err(e); + } + self.retries += 1; + let sleep = if self.retries == 1 { + let mut rng = rand::thread_rng(); + INITIAL_RETRY_SLEEP_BASE_MS + rng.gen_range(0..INITIAL_RETRY_JITTER_MS) + } else { + min( + ((self.retries - 1) * 3) * 1000 + INITIAL_RETRY_SLEEP_BASE_MS, + MAX_RETRY_SLEEP_MS, + ) + }; + RetryResult::Retry(sleep) } - other => other.map(Some), + Err(e) => RetryResult::Err(e), + Ok(r) => RetryResult::Success(r), } } } @@ -79,6 +103,15 @@ fn maybe_spurious(err: &Error) -> bool { return true; } } + + use gix::protocol::transport::IsSpuriousError; + + if let Some(err) = err.downcast_ref::() { + if err.is_spurious() { + return true; + } + } + false } @@ -96,7 +129,7 @@ fn maybe_spurious(err: &Error) -> bool { /// # let download_something = || return Ok(()); /// # let config = Config::default().unwrap(); /// use cargo::util::network; -/// let cargo_result = network::with_retry(&config, || download_something()); +/// let cargo_result = network::retry::with_retry(&config, || download_something()); /// ``` pub fn with_retry(config: &Config, mut callback: F) -> CargoResult where @@ -104,30 +137,14 @@ where { let mut retry = Retry::new(config)?; loop { - if let Some(ret) = retry.r#try(&mut callback)? { - return Ok(ret); + match retry.r#try(&mut callback) { + RetryResult::Success(r) => return Ok(r), + RetryResult::Err(e) => return Err(e), + RetryResult::Retry(sleep) => std::thread::sleep(Duration::from_millis(sleep)), } } } -// When dynamically linked against libcurl, we want to ignore some failures -// when using old versions that don't support certain features. -#[macro_export] -macro_rules! try_old_curl { - ($e:expr, $msg:expr) => { - let result = $e; - if cfg!(target_os = "macos") { - if let Err(e) = result { - warn!("ignoring libcurl {} error: {}", $msg, e); - } - } else { - result.with_context(|| { - anyhow::format_err!("failed to enable {}, is curl not built right?", $msg) - })?; - } - }; -} - #[test] fn with_retry_repeats_the_call_then_works() { use crate::core::Shell; @@ -136,13 +153,17 @@ fn with_retry_repeats_the_call_then_works() { let error1 = HttpNotSuccessful { code: 501, url: "Uri".to_string(), + ip: None, body: Vec::new(), + headers: Vec::new(), } .into(); let error2 = HttpNotSuccessful { code: 502, url: "Uri".to_string(), + ip: None, body: Vec::new(), + headers: Vec::new(), } .into(); let mut results: Vec> = vec![Ok(()), Err(error1), Err(error2)]; @@ -161,13 +182,17 @@ fn with_retry_finds_nested_spurious_errors() { let error1 = anyhow::Error::from(HttpNotSuccessful { code: 501, url: "Uri".to_string(), + ip: None, body: Vec::new(), + headers: Vec::new(), }); let error1 = anyhow::Error::from(error1.context("A non-spurious wrapping err")); let error2 = anyhow::Error::from(HttpNotSuccessful { code: 502, url: "Uri".to_string(), + ip: None, body: Vec::new(), + headers: Vec::new(), }); let error2 = anyhow::Error::from(error2.context("A second chained error")); let mut results: Vec> = vec![Ok(()), Err(error1), Err(error2)]; @@ -177,6 +202,45 @@ fn with_retry_finds_nested_spurious_errors() { assert!(result.is_ok()) } +#[test] +fn default_retry_schedule() { + use crate::core::Shell; + + let spurious = || -> CargoResult<()> { + Err(anyhow::Error::from(HttpNotSuccessful { + code: 500, + url: "Uri".to_string(), + ip: None, + body: Vec::new(), + headers: Vec::new(), + })) + }; + let config = Config::default().unwrap(); + *config.shell() = Shell::from_write(Box::new(Vec::new())); + let mut retry = Retry::new(&config).unwrap(); + match retry.r#try(|| spurious()) { + RetryResult::Retry(sleep) => { + assert!( + sleep >= INITIAL_RETRY_SLEEP_BASE_MS + && sleep < INITIAL_RETRY_SLEEP_BASE_MS + INITIAL_RETRY_JITTER_MS + ); + } + _ => panic!("unexpected non-retry"), + } + match retry.r#try(|| spurious()) { + RetryResult::Retry(sleep) => assert_eq!(sleep, 3500), + _ => panic!("unexpected non-retry"), + } + match retry.r#try(|| spurious()) { + RetryResult::Retry(sleep) => assert_eq!(sleep, 6500), + _ => panic!("unexpected non-retry"), + } + match retry.r#try(|| spurious()) { + RetryResult::Err(_) => {} + _ => panic!("unexpected non-retry"), + } +} + #[test] fn curle_http2_stream_is_spurious() { let code = curl_sys::CURLE_HTTP2_STREAM; diff --git a/src/cargo/util/network/sleep.rs b/src/cargo/util/network/sleep.rs new file mode 100644 index 00000000000..d4105065e29 --- /dev/null +++ b/src/cargo/util/network/sleep.rs @@ -0,0 +1,103 @@ +//! Utility for tracking network requests that will be retried in the future. + +use core::cmp::Ordering; +use std::collections::BinaryHeap; +use std::time::{Duration, Instant}; + +/// A tracker for network requests that have failed, and are awaiting to be +/// retried in the future. +pub struct SleepTracker { + /// This is a priority queue that tracks the time when the next sleeper + /// should awaken (based on the [`Sleeper::wakeup`] property). + heap: BinaryHeap>, +} + +/// An individual network request that is waiting to be retried in the future. +struct Sleeper { + /// The time when this requests should be retried. + wakeup: Instant, + /// Information about the network request. + data: T, +} + +impl PartialEq for Sleeper { + fn eq(&self, other: &Sleeper) -> bool { + self.wakeup == other.wakeup + } +} + +impl PartialOrd for Sleeper { + fn partial_cmp(&self, other: &Sleeper) -> Option { + // This reverses the comparison so that the BinaryHeap tracks the + // entry with the *lowest* wakeup time. + Some(other.wakeup.cmp(&self.wakeup)) + } +} + +impl Eq for Sleeper {} + +impl Ord for Sleeper { + fn cmp(&self, other: &Sleeper) -> Ordering { + self.wakeup.cmp(&other.wakeup) + } +} + +impl SleepTracker { + pub fn new() -> SleepTracker { + SleepTracker { + heap: BinaryHeap::new(), + } + } + + /// Adds a new download that should be retried in the future. + pub fn push(&mut self, sleep: u64, data: T) { + self.heap.push(Sleeper { + wakeup: Instant::now() + .checked_add(Duration::from_millis(sleep)) + .expect("instant should not wrap"), + data, + }); + } + + pub fn len(&self) -> usize { + self.heap.len() + } + + /// Returns any downloads that are ready to go now. + pub fn to_retry(&mut self) -> Vec { + let now = Instant::now(); + let mut result = Vec::new(); + while let Some(next) = self.heap.peek() { + log::debug!("ERIC: now={now:?} next={:?}", next.wakeup); + if next.wakeup < now { + result.push(self.heap.pop().unwrap().data); + } else { + break; + } + } + result + } + + /// Returns the time when the next download is ready to go. + /// + /// Returns None if there are no sleepers remaining. + pub fn time_to_next(&self) -> Option { + self.heap + .peek() + .map(|s| s.wakeup.saturating_duration_since(Instant::now())) + } +} + +#[test] +fn returns_in_order() { + let mut s = SleepTracker::new(); + s.push(3, 3); + s.push(1, 1); + s.push(6, 6); + s.push(5, 5); + s.push(2, 2); + s.push(10000, 10000); + assert_eq!(s.len(), 6); + std::thread::sleep(Duration::from_millis(100)); + assert_eq!(s.to_retry(), &[1, 2, 3, 5, 6]); +} diff --git a/src/cargo/util/profile.rs b/src/cargo/util/profile.rs index c655b5488bc..79b544d98c8 100644 --- a/src/cargo/util/profile.rs +++ b/src/cargo/util/profile.rs @@ -22,6 +22,8 @@ pub struct Profiler { } fn enabled_level() -> Option { + // ALLOWED: for profiling Cargo itself, not intended to be used beyond Cargo contributors. + #[allow(clippy::disallowed_methods)] env::var("CARGO_PROFILE").ok().and_then(|s| s.parse().ok()) } diff --git a/src/cargo/util/progress.rs b/src/cargo/util/progress.rs index f2758485011..bcbc1bc0edc 100644 --- a/src/cargo/util/progress.rs +++ b/src/cargo/util/progress.rs @@ -1,3 +1,5 @@ +//! Support for CLI progress bars. + use std::cmp; use std::time::{Duration, Instant}; @@ -7,13 +9,49 @@ use crate::util::{CargoResult, Config}; use cargo_util::is_ci; use unicode_width::UnicodeWidthChar; +/// CLI progress bar. +/// +/// The `Progress` object can be in an enabled or disabled state. When +/// disabled, calling any of the methods to update it will not display +/// anything. Disabling is typically done by the user with options such as +/// `--quiet` or the `term.progress` config option. +/// +/// There are several methods to update the progress bar and to cause it to +/// update its display. +/// +/// The bar will be removed from the display when the `Progress` object is +/// dropped or [`Progress::clear`] is called. +/// +/// The progress bar has built-in rate limiting to avoid updating the display +/// too fast. It should usually be fine to call [`Progress::tick`] as often as +/// needed, though be cautious if the tick rate is very high or it is +/// expensive to compute the progress value. pub struct Progress<'cfg> { state: Option>, } +/// Indicates the style of information for displaying the amount of progress. +/// +/// See also [`Progress::print_now`] for displaying progress without a bar. pub enum ProgressStyle { + /// Displays progress as a percentage. + /// + /// Example: `Fetch [=====================> ] 88.15%` + /// + /// This is good for large values like number of bytes downloaded. Percentage, + /// Displays progress as a ratio. + /// + /// Example: `Building [===> ] 35/222` + /// + /// This is good for smaller values where the exact number is useful to see. Ratio, + /// Does not display an exact value of how far along it is. + /// + /// Example: `Fetch [===========> ]` + /// + /// This is good for situations where the exact value is an approximation, + /// and thus there isn't anything accurate to display to the user. Indeterminate, } @@ -39,6 +77,16 @@ struct Format { } impl<'cfg> Progress<'cfg> { + /// Creates a new progress bar. + /// + /// The first parameter is the text displayed to the left of the bar, such + /// as "Fetching". + /// + /// The progress bar is not displayed until explicitly updated with one if + /// its methods. + /// + /// The progress bar may be created in a disabled state if the user has + /// disabled progress display (such as with the `--quiet` option). pub fn with_style(name: &str, style: ProgressStyle, cfg: &'cfg Config) -> Progress<'cfg> { // report no progress when -q (for quiet) or TERM=dumb are set // or if running on Continuous Integration service like Travis where the @@ -84,18 +132,33 @@ impl<'cfg> Progress<'cfg> { } } + /// Disables the progress bar, ensuring it won't be displayed. pub fn disable(&mut self) { self.state = None; } + /// Returns whether or not the progress bar is allowed to be displayed. pub fn is_enabled(&self) -> bool { self.state.is_some() } + /// Creates a new `Progress` with the [`ProgressStyle::Percentage`] style. + /// + /// See [`Progress::with_style`] for more information. pub fn new(name: &str, cfg: &'cfg Config) -> Progress<'cfg> { Self::with_style(name, ProgressStyle::Percentage, cfg) } + /// Updates the state of the progress bar. + /// + /// * `cur` should be how far along the progress is. + /// * `max` is the maximum value for the progress bar. + /// * `msg` is a small piece of text to display at the end of the progress + /// bar. It will be truncated with `...` if it does not fit on the + /// terminal. + /// + /// This may not actually update the display if `tick` is being called too + /// quickly. pub fn tick(&mut self, cur: usize, max: usize, msg: &str) -> CargoResult<()> { let s = match &mut self.state { Some(s) => s, @@ -121,6 +184,14 @@ impl<'cfg> Progress<'cfg> { s.tick(cur, max, msg) } + /// Updates the state of the progress bar. + /// + /// This is the same as [`Progress::tick`], but ignores rate throttling + /// and forces the display to be updated immediately. + /// + /// This may be useful for situations where you know you aren't calling + /// `tick` too fast, and accurate information is more important than + /// limiting the console update rate. pub fn tick_now(&mut self, cur: usize, max: usize, msg: &str) -> CargoResult<()> { match self.state { Some(ref mut s) => s.tick(cur, max, msg), @@ -128,6 +199,10 @@ impl<'cfg> Progress<'cfg> { } } + /// Returns whether or not updates are currently being throttled. + /// + /// This can be useful if computing the values for calling the + /// [`Progress::tick`] function may require some expensive work. pub fn update_allowed(&mut self) -> bool { match &mut self.state { Some(s) => s.throttle.allowed(), @@ -135,6 +210,14 @@ impl<'cfg> Progress<'cfg> { } } + /// Displays progress without a bar. + /// + /// The given `msg` is the text to display after the status message. + /// + /// Example: `Downloading 61 crates, remaining bytes: 28.0 MB` + /// + /// This does not have any rate limit throttling, so be careful about + /// calling it too often. pub fn print_now(&mut self, msg: &str) -> CargoResult<()> { match &mut self.state { Some(s) => s.print("", msg), @@ -142,6 +225,7 @@ impl<'cfg> Progress<'cfg> { } } + /// Clears the progress bar from the console. pub fn clear(&mut self) { if let Some(ref mut s) = self.state { s.clear(); diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index 6b429d6a151..9e7c6f63e20 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -239,6 +239,15 @@ impl<'de, P: Deserialize<'de> + Clone> de::Deserialize<'de> for TomlDependency

Vec { + match self { + TomlDependency::Simple(_) => vec![], + TomlDependency::Detailed(detailed) => detailed.other.keys().cloned().collect(), + } + } +} + pub trait ResolveToPath { fn resolve(&self, config: &Config) -> PathBuf; } @@ -288,6 +297,10 @@ pub struct DetailedTomlDependency { lib: Option, /// A platform name, like `x86_64-apple-darwin` target: Option, + /// This is here to provide a way to see the "unused manifest keys" when deserializing + #[serde(skip_serializing)] + #[serde(flatten)] + other: BTreeMap, } // Explicit implementation so we avoid pulling in P: Default @@ -311,6 +324,7 @@ impl Default for DetailedTomlDependency

{ artifact: Default::default(), lib: Default::default(), target: Default::default(), + other: Default::default(), } } } @@ -340,7 +354,7 @@ pub struct TomlManifest { replace: Option>, patch: Option>>, workspace: Option, - badges: Option>>>, + badges: Option, } #[derive(Deserialize, Serialize, Clone, Debug, Default)] @@ -901,16 +915,14 @@ impl<'de> de::Deserialize<'de> for VecStringOrBool { } } -fn version_trim_whitespace<'de, D>( - deserializer: D, -) -> Result, D::Error> +fn version_trim_whitespace<'de, D>(deserializer: D) -> Result where D: de::Deserializer<'de>, { struct Visitor; impl<'de> de::Visitor<'de> for Visitor { - type Value = MaybeWorkspaceField; + type Value = MaybeWorkspaceSemverVersion; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("SemVer version") @@ -963,29 +975,6 @@ pub enum MaybeWorkspace { Workspace(W), } -impl<'de, T: Deserialize<'de>, W: WorkspaceInherit + de::Deserialize<'de>> de::Deserialize<'de> - for MaybeWorkspace -{ - fn deserialize(deserializer: D) -> Result, D::Error> - where - D: de::Deserializer<'de>, - { - let value = serde_value::Value::deserialize(deserializer)?; - - if let Ok(w) = W::deserialize(serde_value::ValueDeserializer::::new( - value.clone(), - )) { - return if w.workspace() { - Ok(MaybeWorkspace::Workspace(w)) - } else { - Err(de::Error::custom("`workspace` cannot be false")) - }; - } - T::deserialize(serde_value::ValueDeserializer::::new(value)) - .map(MaybeWorkspace::Defined) - } -} - impl MaybeWorkspace { fn resolve<'a>( self, @@ -1029,6 +1018,37 @@ impl MaybeWorkspace { type MaybeWorkspaceDependency = MaybeWorkspace; +impl<'de> de::Deserialize<'de> for MaybeWorkspaceDependency { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + let value = serde_value::Value::deserialize(deserializer)?; + + if let Ok(w) = TomlWorkspaceDependency::deserialize(serde_value::ValueDeserializer::< + D::Error, + >::new(value.clone())) + { + return if w.workspace() { + Ok(MaybeWorkspace::Workspace(w)) + } else { + Err(de::Error::custom("`workspace` cannot be false")) + }; + } + TomlDependency::deserialize(serde_value::ValueDeserializer::::new(value)) + .map(MaybeWorkspace::Defined) + } +} + +impl MaybeWorkspaceDependency { + fn unused_keys(&self) -> Vec { + match self { + MaybeWorkspaceDependency::Defined(d) => d.unused_keys(), + MaybeWorkspaceDependency::Workspace(w) => w.other.keys().cloned().collect(), + } + } +} + #[derive(Deserialize, Serialize, Clone, Debug)] #[serde(rename_all = "kebab-case")] pub struct TomlWorkspaceDependency { @@ -1038,6 +1058,10 @@ pub struct TomlWorkspaceDependency { #[serde(rename = "default_features")] default_features2: Option, optional: Option, + /// This is here to provide a way to see the "unused manifest keys" when deserializing + #[serde(skip_serializing)] + #[serde(flatten)] + other: BTreeMap, } impl WorkspaceInherit for TomlWorkspaceDependency { @@ -1123,13 +1147,206 @@ impl TomlWorkspaceDependency { } } -type MaybeWorkspaceField = MaybeWorkspace; +//. This already has a `Deserialize` impl from version_trim_whitespace +type MaybeWorkspaceSemverVersion = MaybeWorkspace; + +type MaybeWorkspaceString = MaybeWorkspace; +impl<'de> de::Deserialize<'de> for MaybeWorkspaceString { + fn deserialize(d: D) -> Result + where + D: de::Deserializer<'de>, + { + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor { + type Value = MaybeWorkspaceString; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + f.write_str("a string or workspace") + } + + fn visit_string(self, value: String) -> Result + where + E: de::Error, + { + Ok(MaybeWorkspaceString::Defined(value)) + } + + fn visit_map(self, map: V) -> Result + where + V: de::MapAccess<'de>, + { + let mvd = de::value::MapAccessDeserializer::new(map); + TomlWorkspaceField::deserialize(mvd).map(MaybeWorkspace::Workspace) + } + } + + d.deserialize_any(Visitor) + } +} + +type MaybeWorkspaceVecString = MaybeWorkspace, TomlWorkspaceField>; +impl<'de> de::Deserialize<'de> for MaybeWorkspaceVecString { + fn deserialize(d: D) -> Result + where + D: de::Deserializer<'de>, + { + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor { + type Value = MaybeWorkspaceVecString; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.write_str("a vector of strings or workspace") + } + fn visit_seq(self, v: A) -> Result + where + A: de::SeqAccess<'de>, + { + let seq = de::value::SeqAccessDeserializer::new(v); + Vec::deserialize(seq).map(MaybeWorkspace::Defined) + } + + fn visit_map(self, map: V) -> Result + where + V: de::MapAccess<'de>, + { + let mvd = de::value::MapAccessDeserializer::new(map); + TomlWorkspaceField::deserialize(mvd).map(MaybeWorkspace::Workspace) + } + } + + d.deserialize_any(Visitor) + } +} + +type MaybeWorkspaceStringOrBool = MaybeWorkspace; +impl<'de> de::Deserialize<'de> for MaybeWorkspaceStringOrBool { + fn deserialize(d: D) -> Result + where + D: de::Deserializer<'de>, + { + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor { + type Value = MaybeWorkspaceStringOrBool; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.write_str("a string, a bool, or workspace") + } + + fn visit_bool(self, v: bool) -> Result + where + E: de::Error, + { + let b = de::value::BoolDeserializer::new(v); + StringOrBool::deserialize(b).map(MaybeWorkspace::Defined) + } + + fn visit_string(self, v: String) -> Result + where + E: de::Error, + { + let string = de::value::StringDeserializer::new(v); + StringOrBool::deserialize(string).map(MaybeWorkspace::Defined) + } + + fn visit_map(self, map: V) -> Result + where + V: de::MapAccess<'de>, + { + let mvd = de::value::MapAccessDeserializer::new(map); + TomlWorkspaceField::deserialize(mvd).map(MaybeWorkspace::Workspace) + } + } + + d.deserialize_any(Visitor) + } +} + +type MaybeWorkspaceVecStringOrBool = MaybeWorkspace; +impl<'de> de::Deserialize<'de> for MaybeWorkspaceVecStringOrBool { + fn deserialize(d: D) -> Result + where + D: de::Deserializer<'de>, + { + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor { + type Value = MaybeWorkspaceVecStringOrBool; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.write_str("a boolean, a vector of strings, or workspace") + } + + fn visit_bool(self, v: bool) -> Result + where + E: de::Error, + { + let b = de::value::BoolDeserializer::new(v); + VecStringOrBool::deserialize(b).map(MaybeWorkspace::Defined) + } + + fn visit_seq(self, v: A) -> Result + where + A: de::SeqAccess<'de>, + { + let seq = de::value::SeqAccessDeserializer::new(v); + VecStringOrBool::deserialize(seq).map(MaybeWorkspace::Defined) + } + + fn visit_map(self, map: V) -> Result + where + V: de::MapAccess<'de>, + { + let mvd = de::value::MapAccessDeserializer::new(map); + TomlWorkspaceField::deserialize(mvd).map(MaybeWorkspace::Workspace) + } + } + + d.deserialize_any(Visitor) + } +} + +type MaybeWorkspaceBtreeMap = + MaybeWorkspace>, TomlWorkspaceField>; + +impl<'de> de::Deserialize<'de> for MaybeWorkspaceBtreeMap { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + let value = serde_value::Value::deserialize(deserializer)?; + + if let Ok(w) = TomlWorkspaceField::deserialize( + serde_value::ValueDeserializer::::new(value.clone()), + ) { + return if w.workspace() { + Ok(MaybeWorkspace::Workspace(w)) + } else { + Err(de::Error::custom("`workspace` cannot be false")) + }; + } + BTreeMap::deserialize(serde_value::ValueDeserializer::::new(value)) + .map(MaybeWorkspace::Defined) + } +} #[derive(Deserialize, Serialize, Clone, Debug)] pub struct TomlWorkspaceField { + #[serde(deserialize_with = "bool_no_false")] workspace: bool, } +fn bool_no_false<'de, D: de::Deserializer<'de>>(deserializer: D) -> Result { + let b: bool = Deserialize::deserialize(deserializer)?; + if b { + Ok(b) + } else { + Err(de::Error::custom("`workspace` cannot be false")) + } +} + impl WorkspaceInherit for TomlWorkspaceField { fn inherit_toml_table(&self) -> &str { "package" @@ -1149,12 +1366,12 @@ impl WorkspaceInherit for TomlWorkspaceField { #[derive(Deserialize, Serialize, Clone, Debug)] #[serde(rename_all = "kebab-case")] pub struct TomlPackage { - edition: Option>, - rust_version: Option>, + edition: Option, + rust_version: Option, name: InternedString, #[serde(deserialize_with = "version_trim_whitespace")] - version: MaybeWorkspaceField, - authors: Option>>, + version: MaybeWorkspaceSemverVersion, + authors: Option, build: Option, metabuild: Option, #[serde(rename = "default-target")] @@ -1162,9 +1379,9 @@ pub struct TomlPackage { #[serde(rename = "forced-target")] forced_target: Option, links: Option, - exclude: Option>>, - include: Option>>, - publish: Option>, + exclude: Option, + include: Option, + publish: Option, workspace: Option, im_a_teapot: Option, autobins: Option, @@ -1174,15 +1391,15 @@ pub struct TomlPackage { default_run: Option, // Package metadata. - description: Option>, - homepage: Option>, - documentation: Option>, - readme: Option>, - keywords: Option>>, - categories: Option>>, - license: Option>, - license_file: Option>, - repository: Option>, + description: Option, + homepage: Option, + documentation: Option, + readme: Option, + keywords: Option, + categories: Option, + license: Option, + license_file: Option, + repository: Option, resolver: Option, // Note that this field must come last due to the way toml serialization @@ -1700,6 +1917,16 @@ impl TomlManifest { let mut inheritable = toml_config.package.clone().unwrap_or_default(); inheritable.update_ws_path(package_root.to_path_buf()); inheritable.update_deps(toml_config.dependencies.clone()); + if let Some(ws_deps) = &inheritable.dependencies { + for (name, dep) in ws_deps { + unused_dep_keys( + name, + "workspace.dependencies", + dep.unused_keys(), + &mut warnings, + ); + } + } let ws_root_config = WorkspaceRootConfig::new( package_root, &toml_config.members, @@ -1898,7 +2125,18 @@ impl TomlManifest { .clone() .resolve_with_self(n, |dep| dep.resolve(n, inheritable, cx))?; let dep = resolved.to_dependency(n, cx, kind)?; - validate_package_name(dep.name_in_toml().as_str(), "dependency name", "")?; + let name_in_toml = dep.name_in_toml().as_str(); + validate_package_name(name_in_toml, "dependency name", "")?; + let kind_name = match kind { + Some(k) => k.kind_table(), + None => "dependencies", + }; + let table_in_toml = if let Some(platform) = &cx.platform { + format!("target.{}.{kind_name}", platform.to_string()) + } else { + kind_name.to_string() + }; + unused_dep_keys(name_in_toml, &table_in_toml, v.unused_keys(), cx.warnings); cx.deps.push(dep); deps.insert(n.to_string(), MaybeWorkspace::Defined(resolved.clone())); } @@ -2426,6 +2664,12 @@ impl TomlManifest { spec ) })?; + unused_dep_keys( + dep.name_in_toml().as_str(), + "replace", + replacement.unused_keys(), + &mut cx.warnings, + ); dep.set_version_req(VersionReq::exact(version)) .lock_version(version); replace.push((spec, dep)); @@ -2435,21 +2679,32 @@ impl TomlManifest { fn patch(&self, cx: &mut Context<'_, '_>) -> CargoResult>> { let mut patch = HashMap::new(); - for (url, deps) in self.patch.iter().flatten() { - let url = match &url[..] { + for (toml_url, deps) in self.patch.iter().flatten() { + let url = match &toml_url[..] { CRATES_IO_REGISTRY => CRATES_IO_INDEX.parse().unwrap(), _ => cx .config - .get_registry_index(url) - .or_else(|_| url.into_url()) + .get_registry_index(toml_url) + .or_else(|_| toml_url.into_url()) .with_context(|| { - format!("[patch] entry `{}` should be a URL or registry name", url) + format!( + "[patch] entry `{}` should be a URL or registry name", + toml_url + ) })?, }; patch.insert( url, deps.iter() - .map(|(name, dep)| dep.to_dependency(name, cx, None)) + .map(|(name, dep)| { + unused_dep_keys( + name, + &format!("patch.{toml_url}",), + dep.unused_keys(), + &mut cx.warnings, + ); + dep.to_dependency(name, cx, None) + }) .collect::>>()?, ); } @@ -2489,6 +2744,18 @@ impl TomlManifest { } } +fn unused_dep_keys( + dep_name: &str, + kind: &str, + unused_keys: Vec, + warnings: &mut Vec, +) { + for unused in unused_keys { + let key = format!("unused manifest key: {kind}.{dep_name}.{unused}"); + warnings.push(key); + } +} + fn inheritable_from_path( config: &Config, workspace_path: PathBuf, diff --git a/src/cargo/util/toml_mut/manifest.rs b/src/cargo/util/toml_mut/manifest.rs index 8c88333608a..f3fc150e12a 100644 --- a/src/cargo/util/toml_mut/manifest.rs +++ b/src/cargo/util/toml_mut/manifest.rs @@ -59,17 +59,9 @@ impl DepTable { /// Keys to the table. pub fn to_table(&self) -> Vec<&str> { if let Some(target) = &self.target { - vec!["target", target, self.kind_table()] + vec!["target", target, self.kind.kind_table()] } else { - vec![self.kind_table()] - } - } - - fn kind_table(&self) -> &str { - match self.kind { - DepKind::Normal => "dependencies", - DepKind::Development => "dev-dependencies", - DepKind::Build => "build-dependencies", + vec![self.kind.kind_table()] } } } @@ -164,7 +156,7 @@ impl Manifest { let mut sections = Vec::new(); for table in DepTable::KINDS { - let dependency_type = table.kind_table(); + let dependency_type = table.kind.kind_table(); // Dependencies can be in the three standard sections... if self .data diff --git a/src/doc/contrib/book.toml b/src/doc/contrib/book.toml index d6697f37f5c..628179c0d8b 100644 --- a/src/doc/contrib/book.toml +++ b/src/doc/contrib/book.toml @@ -8,3 +8,10 @@ git-repository-url = "https://github.com/rust-lang/cargo/tree/master/src/doc/con [output.html.redirect] "/apidoc/cargo/index.html" = "https://doc.rust-lang.org/nightly/nightly-rustc/cargo/" +"/architecture/index.html" = "../implementation/architecture.html" +"/architecture/console.html" = "../implementation/console.html" +"/architecture/subcommands.html" = "../implementation/subcommands.html" +"/architecture/codebase.html" = "https://doc.rust-lang.org/nightly/nightly-rustc/cargo" +"/architecture/compilation.html" = "https://doc.rust-lang.org/nightly/nightly-rustc/cargo" +"/architecture/files.html" = "https://doc.rust-lang.org/nightly/nightly-rustc/cargo" +"/architecture/packages.html" = "https://doc.rust-lang.org/nightly/nightly-rustc/cargo" diff --git a/src/doc/contrib/src/SUMMARY.md b/src/doc/contrib/src/SUMMARY.md index bf0fb38e2b8..643cc5ac79d 100644 --- a/src/doc/contrib/src/SUMMARY.md +++ b/src/doc/contrib/src/SUMMARY.md @@ -6,16 +6,15 @@ - [Working on Cargo](./process/working-on-cargo.md) - [Release process](./process/release.md) - [Unstable features](./process/unstable.md) -- [Architecture](./architecture/index.md) - - [Codebase Overview](./architecture/codebase.md) - - [SubCommands](./architecture/subcommands.md) - - [Console Output](./architecture/console.md) - - [Packages and Resolution](./architecture/packages.md) - - [Compilation](./architecture/compilation.md) - - [Files](./architecture/files.md) +- [Design Principles](./design.md) +- [Implementing a Change](./implementation/index.md) + - [Architecture](./implementation/architecture.md) + - [New subcommands](./implementation/subcommands.md) + - [Console Output](./implementation/console.md) + - [Filesystem](./implementation/filesystem.md) + - [Debugging](./implementation/debugging.md) - [Tests](./tests/index.md) - [Running Tests](./tests/running.md) - [Writing Tests](./tests/writing.md) - [Benchmarking and Profiling](./tests/profiling.md) - [Crater](./tests/crater.md) -- [Design Principles](./design.md) diff --git a/src/doc/contrib/src/architecture/codebase.md b/src/doc/contrib/src/architecture/codebase.md deleted file mode 100644 index 45c6e0e2f1d..00000000000 --- a/src/doc/contrib/src/architecture/codebase.md +++ /dev/null @@ -1,108 +0,0 @@ -# Codebase Overview - -This is a very high-level overview of the Cargo codebase. - -* [`src/bin/cargo`](https://github.com/rust-lang/cargo/tree/master/src/bin/cargo) - --- Cargo is split in a library and a binary. This is the binary side that - handles argument parsing, and then calls into the library to perform the - appropriate subcommand. Each Cargo subcommand is a separate module here. See - [SubCommands](subcommands.md). - -* [`src/cargo/ops`](https://github.com/rust-lang/cargo/tree/master/src/cargo/ops) - --- Every major operation is implemented here. This is where the binary CLI - usually calls into to perform the appropriate action. - - * [`src/cargo/ops/cargo_compile/mod.rs`](https://github.com/rust-lang/cargo/blob/master/src/cargo/ops/cargo_compile/mod.rs) - --- This is the entry point for all the compilation commands. This is a - good place to start if you want to follow how compilation starts and - flows to completion. - -* [`src/cargo/core/resolver`](https://github.com/rust-lang/cargo/tree/master/src/cargo/core/resolver) - --- This is the dependency and feature resolvers. - -* [`src/cargo/core/compiler`](https://github.com/rust-lang/cargo/tree/master/src/cargo/core/compiler) - --- This is the code responsible for running `rustc` and `rustdoc`. - - * [`src/cargo/core/compiler/build_context/mod.rs`](https://github.com/rust-lang/cargo/blob/master/src/cargo/core/compiler/build_context/mod.rs) - --- The `BuildContext` is the result of the "front end" of the build - process. This contains the graph of work to perform and any settings - necessary for `rustc`. After this is built, the next stage of building - is handled in `Context`. - - * [`src/cargo/core/compiler/context`](https://github.com/rust-lang/cargo/blob/master/src/cargo/core/compiler/context/mod.rs) - --- The `Context` is the mutable state used during the build process. This - is the core of the build process, and everything is coordinated through - this. - - * [`src/cargo/core/compiler/fingerprint.rs`](https://github.com/rust-lang/cargo/blob/master/src/cargo/core/compiler/fingerprint.rs) - --- The `fingerprint` module contains all the code that handles detecting - if a crate needs to be recompiled. - -* [`src/cargo/core/source`](https://github.com/rust-lang/cargo/tree/master/src/cargo/core/source) - --- The `Source` trait is an abstraction over different sources of packages. - Sources are uniquely identified by a `SourceId`. Sources are implemented in - the - [`src/cargo/sources`](https://github.com/rust-lang/cargo/tree/master/src/cargo/sources) - directory. - -* [`src/cargo/util`](https://github.com/rust-lang/cargo/tree/master/src/cargo/util) - --- This directory contains generally-useful utility modules. - -* [`src/cargo/util/config`](https://github.com/rust-lang/cargo/tree/master/src/cargo/util/config) - --- This directory contains the config parser. It makes heavy use of - [serde](https://serde.rs/) to merge and translate config values. The - `Config` is usually accessed from the - [`Workspace`](https://github.com/rust-lang/cargo/blob/master/src/cargo/core/workspace.rs), - though references to it are scattered around for more convenient access. - -* [`src/cargo/util/toml`](https://github.com/rust-lang/cargo/tree/master/src/cargo/util/toml) - --- This directory contains the code for parsing `Cargo.toml` files. - - * [`src/cargo/ops/lockfile.rs`](https://github.com/rust-lang/cargo/blob/master/src/cargo/ops/lockfile.rs) - --- This is where `Cargo.lock` files are loaded and saved. - -* [`src/doc`](https://github.com/rust-lang/cargo/tree/master/src/doc) - --- This directory contains Cargo's documentation and man pages. - -* [`src/etc`](https://github.com/rust-lang/cargo/tree/master/src/etc) - --- These are files that get distributed in the `etc` directory in the Rust release. - The man pages are auto-generated by a script in the `src/doc` directory. - -* [`crates`](https://github.com/rust-lang/cargo/tree/master/crates) - --- A collection of independent crates used by Cargo. - -## Extra crates - -Some functionality is split off into separate crates, usually in the -[`crates`](https://github.com/rust-lang/cargo/tree/master/crates) directory. - -* [`cargo-platform`](https://github.com/rust-lang/cargo/tree/master/crates/cargo-platform) - --- This library handles parsing `cfg` expressions. -* [`cargo-test-macro`](https://github.com/rust-lang/cargo/tree/master/crates/cargo-test-macro) - --- This is a proc-macro used by the test suite to define tests. More - information can be found at [`cargo_test` - attribute](../tests/writing.md#cargo_test-attribute). -* [`cargo-test-support`](https://github.com/rust-lang/cargo/tree/master/crates/cargo-test-support) - --- This contains a variety of code to support [writing - tests](../tests/writing.md). -* [`cargo-util`](https://github.com/rust-lang/cargo/tree/master/crates/cargo-util) - --- This contains general utility code that is shared between cargo and the - testsuite. -* [`crates-io`](https://github.com/rust-lang/cargo/tree/master/crates/crates-io) - --- This contains code for accessing the crates.io API. -* [`credential`](https://github.com/rust-lang/cargo/tree/master/crates/credential) - --- This subdirectory contains several packages for implementing the - experimental - [credential-process](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#credential-process) - feature. -* [`home`](https://github.com/rust-lang/cargo/tree/master/crates/home) --- This library is shared between cargo and rustup and is used for finding their home directories. - This is not directly depended upon with a `path` dependency; cargo uses the version from crates.io. - It is intended to be versioned and published independently of Rust's release system. - Whenever a change needs to be made, bump the version in Cargo.toml and `cargo publish` it manually, and then update cargo's `Cargo.toml` to depend on the new version. -* [`mdman`](https://github.com/rust-lang/cargo/tree/master/crates/mdman) - --- This is a utility for generating cargo's man pages. See [Building the man - pages](https://github.com/rust-lang/cargo/tree/master/src/doc#building-the-man-pages) - for more information. -* [`resolver-tests`](https://github.com/rust-lang/cargo/tree/master/crates/resolver-tests) - --- This is a dedicated package that defines tests for the [dependency - resolver](../architecture/packages.md#resolver). diff --git a/src/doc/contrib/src/architecture/compilation.md b/src/doc/contrib/src/architecture/compilation.md deleted file mode 100644 index ecccee07152..00000000000 --- a/src/doc/contrib/src/architecture/compilation.md +++ /dev/null @@ -1,39 +0,0 @@ -# Compilation - -The [`Unit`] is the primary data structure representing a single execution of -the compiler. It (mostly) contains all the information needed to determine -which flags to pass to the compiler. - -The entry to the compilation process is located in the [`cargo_compile`] -module. The compilation can be conceptually broken into these steps: - -1. Perform dependency resolution (see [the resolution chapter]). -2. Generate the root `Unit`s, the things the user requested to compile on the - command-line. This is done in the [`unit_generator`] module. -3. Starting from the root `Unit`s, generate the [`UnitGraph`] by walking the - dependency graph from the resolver. The `UnitGraph` contains all of the - `Unit` structs, and information about the dependency relationships between - units. This is done in the [`unit_dependencies`] module. -4. Construct the [`BuildContext`] with all of the information collected so - far. This is the end of the "front end" of compilation. -5. Create a [`Context`], a large, mutable data structure that coordinates the - compilation process. -6. The [`Context`] will create a [`JobQueue`], a data structure that tracks - which units need to be built. -7. [`drain_the_queue`] does the compilation process. This is the only point in - Cargo that currently uses threads. -8. The result of the compilation is stored in the [`Compilation`] struct. This - can be used for various things, such as running tests after the compilation - has finished. - -[`cargo_compile`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/ops/cargo_compile/mod.rs -[`unit_generator`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/ops/cargo_compile/unit_generator.rs -[`UnitGraph`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/core/compiler/unit_graph.rs -[the resolution chapter]: packages.md -[`Unit`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/core/compiler/unit.rs -[`unit_dependencies`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/core/compiler/unit_dependencies.rs -[`BuildContext`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/core/compiler/build_context/mod.rs -[`Context`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/core/compiler/context/mod.rs -[`JobQueue`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/core/compiler/job_queue.rs -[`drain_the_queue`]: https://github.com/rust-lang/cargo/blob/e4b65bdc80f2a293447f2f6a808fa7c84bf9a357/src/cargo/core/compiler/job_queue.rs#L623-L634 -[`Compilation`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/core/compiler/compilation.rs diff --git a/src/doc/contrib/src/architecture/files.md b/src/doc/contrib/src/architecture/files.md deleted file mode 100644 index 2e6e02b0790..00000000000 --- a/src/doc/contrib/src/architecture/files.md +++ /dev/null @@ -1,67 +0,0 @@ -# Files - -This chapter gives some pointers on where to start looking at Cargo's on-disk -data file structures. - -* [`Layout`] is the abstraction for the `target` directory. It handles locking - the target directory, and providing paths to the parts inside. There is a - separate `Layout` for each "target". -* [`Resolve`] contains the contents of the `Cargo.lock` file. See the [`encode`] - module for the different `Cargo.lock` formats. -* [`TomlManifest`] contains the contents of the `Cargo.toml` file. It is translated - to a [`Manifest`] object for some simplification, and the `Manifest` is stored - in a [`Package`]. -* The [`fingerprint`] module deals with the fingerprint information stored in - `target/debug/.fingerprint`. This tracks whether or not a crate needs to be - rebuilt. -* `cargo install` tracks its installed files with some metadata in - `$CARGO_HOME`. The metadata is managed in the - [`common_for_install_and_uninstall`] module. -* Git sources are cached in `$CARGO_HOME/git`. The code for this cache is in - the [`git`] source module. -* Registries are cached in `$CARGO_HOME/registry`. There are three parts, the - index, the compressed `.crate` files, and the extracted sources of those - crate files. - * Management of the registry cache can be found in the [`registry`] source - module. Note that this includes an on-disk cache as an optimization for - accessing the git repository. - * Saving of `.crate` files is handled by the [`RemoteRegistry`]. - * Extraction of `.crate` files is handled by the [`RegistrySource`]. - * There is a lock for the package cache. Code must be careful, because - this lock must be obtained manually. See - [`Config::acquire_package_cache_lock`]. - -[`Layout`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/core/compiler/layout.rs -[`Resolve`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/core/resolver/resolve.rs -[`encode`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/core/resolver/encode.rs -[`TomlManifest`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/util/toml/mod.rs -[`Manifest`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/core/manifest.rs -[`Package`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/core/package.rs -[`common_for_install_and_uninstall`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/ops/common_for_install_and_uninstall.rs -[`git`]: https://github.com/rust-lang/cargo/tree/master/src/cargo/sources/git -[`registry`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/sources/registry/mod.rs -[`RemoteRegistry`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/sources/registry/remote.rs -[`RegistrySource`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/sources/registry/mod.rs -[`Config::acquire_package_cache_lock`]: https://github.com/rust-lang/cargo/blob/e4b65bdc80f2a293447f2f6a808fa7c84bf9a357/src/cargo/util/config/mod.rs#L1261-L1266 - -## Filesystems - -Cargo tends to get run on a very wide array of file systems. Different file -systems can have a wide range of capabilities, and Cargo should strive to do -its best to handle them. Some examples of issues to deal with: - -* Not all file systems support locking. Cargo tries to detect if locking is - supported, and if not, will ignore lock errors. This isn't ideal, but it is - difficult to deal with. -* The [`fs::canonicalize`] function doesn't work on all file systems - (particularly some Windows file systems). If that function is used, there - should be a fallback if it fails. This function will also return `\\?\` - style paths on Windows, which can have some issues (such as some tools not - supporting them, or having issues with relative paths). -* Timestamps can be unreliable. The [`fingerprint`] module has a deeper - discussion of this. One example is that Docker cache layers will erase the - fractional part of the time stamp. -* Symlinks are not always supported, particularly on Windows. - -[`fingerprint`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/core/compiler/fingerprint.rs -[`fs::canonicalize`]: https://doc.rust-lang.org/std/fs/fn.canonicalize.html diff --git a/src/doc/contrib/src/architecture/index.md b/src/doc/contrib/src/architecture/index.md deleted file mode 100644 index fded5fc4b89..00000000000 --- a/src/doc/contrib/src/architecture/index.md +++ /dev/null @@ -1,8 +0,0 @@ -# Architecture Overview - -This chapter gives a very high-level overview of Cargo's architecture. This is -intended to give you links into the code which is hopefully commented with -more in-depth information. - -If you feel something is missing that would help you, feel free to ask on -Zulip. diff --git a/src/doc/contrib/src/architecture/packages.md b/src/doc/contrib/src/architecture/packages.md deleted file mode 100644 index 626714bf468..00000000000 --- a/src/doc/contrib/src/architecture/packages.md +++ /dev/null @@ -1,92 +0,0 @@ -# Packages and Resolution - -## Workspaces - -The [`Workspace`] object is usually created very early by calling the -[`workspace`][ws-method] helper method. This discovers the root of the -workspace, and loads all the workspace members as a [`Package`] object. Each -package corresponds to a single `Cargo.toml` (which is deserialized into a -[`Manifest`]), and may define several [`Target`]s, such as the library, -binaries, integration test or examples. Targets are crates (each target -defines a crate root, like `src/lib.rs` or `examples/foo.rs`) and are what is -actually compiled by `rustc`. - -## Packages and Sources - -There are several data structures that are important to understand how -packages are found and loaded: - -* [`Package`] --- A package, which is a `Cargo.toml` manifest and its associated - source files. - * [`PackageId`] --- A unique identifier for a package. -* [`Source`] --- An abstraction for something that can fetch packages (a remote - registry, a git repo, the local filesystem, etc.). Check out the [source - implementations] for all the details about registries, indexes, git - dependencies, etc. - * [`SourceId`] --- A unique identifier for a source. -* [`SourceMap`] --- Map of all available sources. -* [`PackageRegistry`] --- This is the main interface for how the dependency - resolver finds packages. It contains the `SourceMap`, and handles things - like the `[patch]` table. The `Registry` trait provides a generic interface - to the `PackageRegistry`, but this is only used for providing an alternate - implementation of the `PackageRegistry` for testing. The dependency resolver - sends a query to the `PackageRegistry` to "get me all packages that match - this dependency declaration". -* [`Summary`] --- A summary is a subset of a [`Manifest`], and is essentially - the information that can be found in a registry index. Queries against the - `PackageRegistry` yields a `Summary`. The resolver uses the summary - information to build the dependency graph. -* [`PackageSet`] --- Contains all of the `Package` objects. This works with the - [`Downloads`] struct to coordinate downloading packages. It has a reference - to the `SourceMap` to get the `Source` objects which tell the `Downloads` - struct which URLs to fetch. - -All of these come together in the [`ops::resolve`] module. This module -contains the primary functions for performing resolution (described below). It -also handles downloading of packages. It is essentially where all of the data -structures above come together. - -## Resolver - -[`Resolve`] is the representation of a directed graph of package dependencies, -which uses [`PackageId`]s for nodes. This is the data structure that is saved -to the `Cargo.lock` file. If there is no lock file, Cargo constructs a resolve -by finding a graph of packages which matches declared dependency specification -according to SemVer. - -[`ops::resolve`] is the front-end for creating a `Resolve`. It handles loading -the `Cargo.lock` file, checking if it needs updating, etc. - -Resolution is currently performed twice. It is performed once with all -features enabled. This is the resolve that gets saved to `Cargo.lock`. It then -runs again with only the specific features the user selected on the -command-line. Ideally this second run will get removed in the future when -transitioning to the new feature resolver. - -### Feature resolver - -A new feature-specific resolver was added in 2020 which adds more -sophisticated feature resolution. It is located in the [`resolver::features`] -module. The original dependency resolver still performs feature unification, -as it can help reduce the dependencies it has to consider during resolution -(rather than assuming every optional dependency of every package is enabled). -Checking if a feature is enabled must go through the new feature resolver. - - -[`Workspace`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/core/workspace.rs -[ws-method]: https://github.com/rust-lang/cargo/blob/e4b65bdc80f2a293447f2f6a808fa7c84bf9a357/src/cargo/util/command_prelude.rs#L298-L318 -[`Package`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/core/package.rs -[`Target`]: https://github.com/rust-lang/cargo/blob/e4b65bdc80f2a293447f2f6a808fa7c84bf9a357/src/cargo/core/manifest.rs#L181-L206 -[`Manifest`]: https://github.com/rust-lang/cargo/blob/e4b65bdc80f2a293447f2f6a808fa7c84bf9a357/src/cargo/core/manifest.rs#L27-L51 -[`Source`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/core/source/mod.rs -[`SourceId`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/core/source/source_id.rs -[`SourceMap`]: https://github.com/rust-lang/cargo/blob/e4b65bdc80f2a293447f2f6a808fa7c84bf9a357/src/cargo/core/source/mod.rs#L245-L249 -[`PackageRegistry`]: https://github.com/rust-lang/cargo/blob/e4b65bdc80f2a293447f2f6a808fa7c84bf9a357/src/cargo/core/registry.rs#L36-L81 -[`ops::resolve`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/ops/resolve.rs -[`resolver::features`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/core/resolver/features.rs#L259 -[source implementations]: https://github.com/rust-lang/cargo/tree/master/src/cargo/sources -[`PackageId`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/core/package_id.rs -[`Summary`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/core/summary.rs -[`PackageSet`]: https://github.com/rust-lang/cargo/blob/e4b65bdc80f2a293447f2f6a808fa7c84bf9a357/src/cargo/core/package.rs#L283-L296 -[`Downloads`]: https://github.com/rust-lang/cargo/blob/e4b65bdc80f2a293447f2f6a808fa7c84bf9a357/src/cargo/core/package.rs#L298-L352 -[`Resolve`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/core/resolver/resolve.rs diff --git a/src/doc/contrib/src/implementation/architecture.md b/src/doc/contrib/src/implementation/architecture.md new file mode 100644 index 00000000000..b712c4fe501 --- /dev/null +++ b/src/doc/contrib/src/implementation/architecture.md @@ -0,0 +1,5 @@ +# Architecture Overview + +See the +[nightly docs](https://doc.rust-lang.org/nightly/nightly-rustc/cargo/index.html) +for an overview of `cargo`s architecture and links out to further details. diff --git a/src/doc/contrib/src/architecture/console.md b/src/doc/contrib/src/implementation/console.md similarity index 75% rename from src/doc/contrib/src/architecture/console.md rename to src/doc/contrib/src/implementation/console.md index 2c5412b8c46..a73d232e0ce 100644 --- a/src/doc/contrib/src/architecture/console.md +++ b/src/doc/contrib/src/implementation/console.md @@ -55,28 +55,4 @@ Some guidelines for Cargo's output: and use multiple sentences. This should probably be improved sometime in the future to be more structured. -## Debug logging - -Cargo uses the [`env_logger`] crate to display debug log messages. The -`CARGO_LOG` environment variable can be set to enable debug logging, with a -value such as `trace`, `debug`, or `warn`. It also supports filtering for -specific modules. Feel free to use the standard [`log`] macros to help with -diagnosing problems. - -```sh -# Outputs all logs with levels debug and higher -CARGO_LOG=debug cargo generate-lockfile - -# Don't forget that you can filter by module as well -CARGO_LOG=cargo::core::resolver=trace cargo generate-lockfile - -# This will print lots of info about the download process. `trace` prints even more. -CARGO_HTTP_DEBUG=true CARGO_LOG=cargo::ops::registry=debug cargo fetch - -# This is an important command for diagnosing fingerprint issues. -CARGO_LOG=cargo::core::compiler::fingerprint=trace cargo build -``` - -[`env_logger`]: https://docs.rs/env_logger -[`log`]: https://docs.rs/log [`anyhow`]: https://docs.rs/anyhow diff --git a/src/doc/contrib/src/implementation/debugging.md b/src/doc/contrib/src/implementation/debugging.md new file mode 100644 index 00000000000..e148d72c36b --- /dev/null +++ b/src/doc/contrib/src/implementation/debugging.md @@ -0,0 +1,26 @@ +# Debugging + +## Logging + +Cargo uses the [`env_logger`] crate to display debug log messages. The +`CARGO_LOG` environment variable can be set to enable debug logging, with a +value such as `trace`, `debug`, or `warn`. It also supports filtering for +specific modules. Feel free to use the standard [`log`] macros to help with +diagnosing problems. + +```sh +# Outputs all logs with levels debug and higher +CARGO_LOG=debug cargo generate-lockfile + +# Don't forget that you can filter by module as well +CARGO_LOG=cargo::core::resolver=trace cargo generate-lockfile + +# This will print lots of info about the download process. `trace` prints even more. +CARGO_HTTP_DEBUG=true CARGO_LOG=cargo::ops::registry=debug cargo fetch + +# This is an important command for diagnosing fingerprint issues. +CARGO_LOG=cargo::core::compiler::fingerprint=trace cargo build +``` + +[`env_logger`]: https://docs.rs/env_logger +[`log`]: https://docs.rs/log diff --git a/src/doc/contrib/src/implementation/filesystem.md b/src/doc/contrib/src/implementation/filesystem.md new file mode 100644 index 00000000000..0f70c5833f0 --- /dev/null +++ b/src/doc/contrib/src/implementation/filesystem.md @@ -0,0 +1,21 @@ +# Filesystem + +Cargo tends to get run on a very wide array of file systems. Different file +systems can have a wide range of capabilities, and Cargo should strive to do +its best to handle them. Some examples of issues to deal with: + +* Not all file systems support locking. Cargo tries to detect if locking is + supported, and if not, will ignore lock errors. This isn't ideal, but it is + difficult to deal with. +* The [`fs::canonicalize`] function doesn't work on all file systems + (particularly some Windows file systems). If that function is used, there + should be a fallback if it fails. This function will also return `\\?\` + style paths on Windows, which can have some issues (such as some tools not + supporting them, or having issues with relative paths). +* Timestamps can be unreliable. The [`fingerprint`] module has a deeper + discussion of this. One example is that Docker cache layers will erase the + fractional part of the time stamp. +* Symlinks are not always supported, particularly on Windows. + +[`fingerprint`]: https://github.com/rust-lang/cargo/blob/master/src/cargo/core/compiler/fingerprint.rs +[`fs::canonicalize`]: https://doc.rust-lang.org/std/fs/fn.canonicalize.html diff --git a/src/doc/contrib/src/implementation/index.md b/src/doc/contrib/src/implementation/index.md new file mode 100644 index 00000000000..ad7c80d5e8b --- /dev/null +++ b/src/doc/contrib/src/implementation/index.md @@ -0,0 +1,6 @@ +# Implementing a Change + +This chapter gives an overview of what you need to know in making a change to cargo. + +If you feel something is missing that would help you, feel free to ask on +[Zulip](https://rust-lang.zulipchat.com/#narrow/stream/246057-t-cargo). diff --git a/src/doc/contrib/src/architecture/subcommands.md b/src/doc/contrib/src/implementation/subcommands.md similarity index 98% rename from src/doc/contrib/src/architecture/subcommands.md rename to src/doc/contrib/src/implementation/subcommands.md index bdb586c24ed..9f5da35550f 100644 --- a/src/doc/contrib/src/architecture/subcommands.md +++ b/src/doc/contrib/src/implementation/subcommands.md @@ -1,4 +1,4 @@ -# SubCommands +# New Subcommands Cargo is a single binary composed of a set of [`clap`] subcommands. All subcommands live in [`src/bin/cargo/commands`] directory. diff --git a/src/doc/contrib/src/process/index.md b/src/doc/contrib/src/process/index.md index af0f030b70c..348c49ba944 100644 --- a/src/doc/contrib/src/process/index.md +++ b/src/doc/contrib/src/process/index.md @@ -23,6 +23,9 @@ changes, and sets the direction for the project. The team meets on a weekly basis on a video chat. If you are interested in participating, feel free to contact us on [Zulip]. +If you would like more direct mentorship, you can join our +[office hours](https://github.com/rust-lang/cargo/wiki/Office-Hours). + ## Roadmap The [Roadmap Project Board] is used for tracking major initiatives. This gives diff --git a/src/doc/contrib/src/process/release.md b/src/doc/contrib/src/process/release.md index 03074108c69..f0de267c8c8 100644 --- a/src/doc/contrib/src/process/release.md +++ b/src/doc/contrib/src/process/release.md @@ -46,7 +46,7 @@ subup --up-branch update-cargo \ --commit-message "Update cargo" \ --test="src/tools/linkchecker tidy \ src/tools/cargo \ - src/tools/rustfmt \ + src/tools/rustfmt" \ src/tools/cargo ``` @@ -59,7 +59,7 @@ subup --up-branch update-beta-cargo \ --commit-message "[beta] Update cargo" \ --test="src/tools/linkchecker tidy \ src/tools/cargo \ - src/tools/rustfmt \ + src/tools/rustfmt" \ rust-1.66.0:src/tools/cargo ``` diff --git a/src/doc/contrib/src/tests/running.md b/src/doc/contrib/src/tests/running.md index dc306fbb4b2..e91702f96bd 100644 --- a/src/doc/contrib/src/tests/running.md +++ b/src/doc/contrib/src/tests/running.md @@ -40,6 +40,16 @@ the `CARGO_RUN_BUILD_STD_TESTS=1` environment variable and running `cargo test `rust-src` component installed with `rustup component add rust-src --toolchain=nightly`. +## Running with `gitoxide` as default git backend in tests + +By default, the `git2` backend is used for most git operations. As tests need to explicitly +opt-in to use nightly features and feature flags, adjusting all tests to run with nightly +and `-Zgitoxide` is unfeasible. + +This is why the private environment variable named `__CARGO_USE_GITOXIDE_INSTEAD_OF_GIT2` can be +set while running tests to automatically enable the `-Zgitoxide` flag implicitly, allowing to +test `gitoxide` for the entire cargo test suite. + ## Running public network tests Some (very rare) tests involve connecting to the public internet. diff --git a/src/doc/contrib/src/tests/writing.md b/src/doc/contrib/src/tests/writing.md index a84dd5d6a41..9c56b783d53 100644 --- a/src/doc/contrib/src/tests/writing.md +++ b/src/doc/contrib/src/tests/writing.md @@ -154,6 +154,48 @@ If you need to test with registry dependencies, see If you need to test git dependencies, see [`support::git`] to create a git dependency. +#### Cross compilation + +There are some utilities to help support tests that need to work against a +target other than the host. See [Running cross +tests](running.md#running-cross-tests) for more an introduction on cross +compilation tests. + +Tests that need to do cross-compilation should include this at the top of the +test to disable it in scenarios where cross compilation isn't available: + +```rust,ignore +if cargo_test_support::cross_compile::disabled() { + return; +} +``` + +The name of the target can be fetched with the [`cross_compile::alternate()`] +function. The name of the host target can be fetched with +[`cargo_test_support::rustc_host()`]. + +The cross-tests need to distinguish between targets which can *build* versus +those which can actually *run* the resulting executable. Unfortunately, macOS is +currently unable to run an alternate target (Apple removed 32-bit support a +long time ago). For building, `x86_64-apple-darwin` will target +`x86_64-apple-ios` as its alternate. However, the iOS target can only execute +binaries if the iOS simulator is installed and configured. The simulator is +not available in CI, so all tests that need to run cross-compiled binaries are +disabled on CI. If you are running on macOS locally, and have the simulator +installed, then it should be able to run them. + +If the test needs to run the cross-compiled binary, then it should have +something like this to exit the test before doing so: + +```rust,ignore +if cargo_test_support::cross_compile::can_run_on_host() { + return; +} +``` + +[`cross_compile::alternate()`]: https://github.com/rust-lang/cargo/blob/d58902e22e148426193cf3b8c4449fd3c05c0afd/crates/cargo-test-support/src/cross_compile.rs#L208-L225 +[`cargo_test_support::rustc_host()`]: https://github.com/rust-lang/cargo/blob/d58902e22e148426193cf3b8c4449fd3c05c0afd/crates/cargo-test-support/src/lib.rs#L1137-L1140 + ### UI Tests UI Tests are a bit more spread out and generally look like: diff --git a/src/doc/man/cargo-add.md b/src/doc/man/cargo-add.md index c9bb2376e0c..c441a82b2ff 100644 --- a/src/doc/man/cargo-add.md +++ b/src/doc/man/cargo-add.md @@ -81,13 +81,12 @@ Add as a [build dependency](../reference/specifying-dependencies.html#build-depe {{#option "`--target` _target_" }} Add as a dependency to the [given target platform](../reference/specifying-dependencies.html#platform-specific-dependencies). + +To avoid unexpected shell expansions, you may use quotes around each target, e.g., `--target 'cfg(unix)'`. {{/option}} {{/options}} - - - ### Dependency options {{#options}} @@ -169,5 +168,9 @@ Add dependencies to only the specified package. cargo add serde serde_json -F serde/derive +5. Add `windows` as a platform specific dependency on `cfg(windows)` + + cargo add windows --target 'cfg(windows)' + ## SEE ALSO {{man "cargo" 1}}, {{man "cargo-remove" 1}} diff --git a/src/doc/man/cargo-bench.md b/src/doc/man/cargo-bench.md index 32c98dadaeb..80785891b5b 100644 --- a/src/doc/man/cargo-bench.md +++ b/src/doc/man/cargo-bench.md @@ -56,6 +56,14 @@ debugger. [`bench` profile]: ../reference/profiles.html#bench +### Working directory of benchmarks + +The working directory of every benchmark is set to the root directory of the +package the benchmark belongs to. +Setting the working directory of benchmarks to the package's root directory +makes it possible for benchmarks to reliably access the package's files using +relative paths, regardless from where `cargo bench` was executed from. + ## OPTIONS ### Benchmark Options diff --git a/src/doc/man/cargo-install.md b/src/doc/man/cargo-install.md index 14c2296c228..31c3d604830 100644 --- a/src/doc/man/cargo-install.md +++ b/src/doc/man/cargo-install.md @@ -179,6 +179,8 @@ See also the `--profile` option for choosing a specific profile by name. {{> options-profile }} +{{> options-ignore-rust-version }} + {{> options-timings }} {{/options}} diff --git a/src/doc/man/cargo-login.md b/src/doc/man/cargo-login.md index 11c663c46c6..54c823d2d8d 100644 --- a/src/doc/man/cargo-login.md +++ b/src/doc/man/cargo-login.md @@ -48,4 +48,4 @@ Take care to keep the token secret, it should not be shared with anyone else. cargo login ## SEE ALSO -{{man "cargo" 1}}, {{man "cargo-publish" 1}} +{{man "cargo" 1}}, {{man "cargo-logout" 1}}, {{man "cargo-publish" 1}} diff --git a/src/doc/man/cargo-logout.md b/src/doc/man/cargo-logout.md new file mode 100644 index 00000000000..f9c0db58cd6 --- /dev/null +++ b/src/doc/man/cargo-logout.md @@ -0,0 +1,57 @@ +# cargo-logout(1) + +## NAME + +cargo-logout --- Remove an API token from the registry locally + +## SYNOPSIS + +`cargo logout` [_options_] + +## DESCRIPTION + +This command will remove the API token from the local credential storage. +Credentials are stored in `$CARGO_HOME/credentials.toml` where `$CARGO_HOME` +defaults to `.cargo` in your home directory. + +If `--registry` is not specified, then the credentials for the default +registry will be removed (configured by +[`registry.default`](../reference/config.html#registrydefault), which defaults +to ). + +This will not revoke the token on the server. If you need to revoke the token, +visit the registry website and follow its instructions (see + to revoke the token for ). + +## OPTIONS + +### Logout Options + +{{#options}} +{{> options-registry }} +{{/options}} + +### Display Options + +{{#options}} +{{> options-display }} +{{/options}} + +{{> section-options-common }} + +{{> section-environment }} + +{{> section-exit-status }} + +## EXAMPLES + +1. Remove the default registry token: + + cargo logout + +2. Remove the token for a specific registry: + + cargo logout --registry my-registry + +## SEE ALSO +{{man "cargo" 1}}, {{man "cargo-login" 1}} diff --git a/src/doc/man/cargo-pkgid.md b/src/doc/man/cargo-pkgid.md index 47ed133f9b5..3c1689b5abd 100644 --- a/src/doc/man/cargo-pkgid.md +++ b/src/doc/man/cargo-pkgid.md @@ -31,7 +31,7 @@ _name_`@`_version_ | `bitflags@1.0.4` _url_ | `https://github.com/rust-lang/cargo` _url_`#`_version_ | `https://github.com/rust-lang/cargo#0.33.0` _url_`#`_name_ | `https://github.com/rust-lang/crates.io-index#bitflags` -_url_`#`_name_`:`_version_ | `https://github.com/rust-lang/cargo#crates-io@0.21.0` +_url_`#`_name_`@`_version_ | `https://github.com/rust-lang/cargo#crates-io@0.21.0` ## OPTIONS diff --git a/src/doc/man/cargo-remove.md b/src/doc/man/cargo-remove.md index 0722e6e53de..e38589ccb08 100644 --- a/src/doc/man/cargo-remove.md +++ b/src/doc/man/cargo-remove.md @@ -30,6 +30,8 @@ Remove as a [build dependency](../reference/specifying-dependencies.html#build-d {{#option "`--target` _target_" }} Remove as a dependency to the [given target platform](../reference/specifying-dependencies.html#platform-specific-dependencies). + +To avoid unexpected shell expansions, you may use quotes around each target, e.g., `--target 'cfg(unix)'`. {{/option}} {{/options}} diff --git a/src/doc/man/cargo-run.md b/src/doc/man/cargo-run.md index 4b6b935242e..034a35f23eb 100644 --- a/src/doc/man/cargo-run.md +++ b/src/doc/man/cargo-run.md @@ -17,6 +17,10 @@ All the arguments following the two dashes (`--`) are passed to the binary to run. If you're passing arguments to both Cargo and the binary, the ones after `--` go to the binary, the ones before go to Cargo. +Unlike {{man "cargo-test" 1}} and {{man "cargo-bench" 1}}, `cargo run` sets the +working directory of the binary executed to the current working directory, same +as if it was executed in the shell directly. + ## OPTIONS {{> section-options-package }} diff --git a/src/doc/man/cargo-rustc.md b/src/doc/man/cargo-rustc.md index f3b37234c0c..18c0856f206 100644 --- a/src/doc/man/cargo-rustc.md +++ b/src/doc/man/cargo-rustc.md @@ -74,7 +74,7 @@ See the [the reference](../reference/profiles.html) for more details on profiles {{#option "`--crate-type` _crate-type_"}} Build for the given crate type. This flag accepts a comma-separated list of 1 or more crate types, of which the allowed values are the same as `crate-type` -field in the manifest for configurating a Cargo target. See +field in the manifest for configuring a Cargo target. See [`crate-type` field](../reference/cargo-targets.html#the-crate-type-field) for possible values. diff --git a/src/doc/man/cargo-test.md b/src/doc/man/cargo-test.md index 0b6da16ca5e..3dce146e60d 100644 --- a/src/doc/man/cargo-test.md +++ b/src/doc/man/cargo-test.md @@ -57,6 +57,14 @@ and may change in the future; beware of depending on it. See the [rustdoc book](https://doc.rust-lang.org/rustdoc/) for more information on writing doc tests. +### Working directory of tests + +The working directory of every test is set to the root directory of the package +the test belongs to. +Setting the working directory of tests to the package's root directory makes it +possible for tests to reliably access the package's files using relative paths, +regardless from where `cargo test` was executed from. + ## OPTIONS ### Test Options diff --git a/src/doc/man/cargo.md b/src/doc/man/cargo.md index 2d71fc4d75c..3b1c62e3253 100644 --- a/src/doc/man/cargo.md +++ b/src/doc/man/cargo.md @@ -102,6 +102,9 @@ available at . {{man "cargo-login" 1}}\     Save an API token from the registry locally. +{{man "cargo-logout" 1}}\ +    Remove an API token from the registry locally. + {{man "cargo-owner" 1}}\     Manage the owners of a crate on the registry. diff --git a/src/doc/man/generated_txt/cargo-add.txt b/src/doc/man/generated_txt/cargo-add.txt index 8cd30eba335..ac332a44e72 100644 --- a/src/doc/man/generated_txt/cargo-add.txt +++ b/src/doc/man/generated_txt/cargo-add.txt @@ -76,7 +76,8 @@ OPTIONS Add as a dependency to the given target platform . - + To avoid unexpected shell expansions, you may use quotes around each + target, e.g., --target 'cfg(unix)'. Dependency options --dry-run @@ -190,7 +191,14 @@ OPTIONS Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as the directories - searched for discovering .cargo/config.toml, for example. + searched for discovering .cargo/config.toml, for example. This + option must appear before the command name, for example cargo -C + path/to/my-project build. + + This option is only available on the nightly channel + and + requires the -Z unstable-options flag to enable (see #10098 + ). -h, --help Prints help information. @@ -226,6 +234,10 @@ EXAMPLES cargo add serde serde_json -F serde/derive + 5. Add windows as a platform specific dependency on cfg(windows) + + cargo add windows --target 'cfg(windows)' + SEE ALSO cargo(1), cargo-remove(1) diff --git a/src/doc/man/generated_txt/cargo-bench.txt b/src/doc/man/generated_txt/cargo-bench.txt index aaa495d4639..1ca72d577af 100644 --- a/src/doc/man/generated_txt/cargo-bench.txt +++ b/src/doc/man/generated_txt/cargo-bench.txt @@ -49,6 +49,13 @@ DESCRIPTION switch to the dev profile. You can then run the debug-enabled benchmark within a debugger. + Working directory of benchmarks + The working directory of every benchmark is set to the root directory of + the package the benchmark belongs to. Setting the working directory of + benchmarks to the package’s root directory makes it possible for + benchmarks to reliably access the package’s files using relative + paths, regardless from where cargo bench was executed from. + OPTIONS Benchmark Options --no-run @@ -369,7 +376,14 @@ OPTIONS Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as the directories - searched for discovering .cargo/config.toml, for example. + searched for discovering .cargo/config.toml, for example. This + option must appear before the command name, for example cargo -C + path/to/my-project build. + + This option is only available on the nightly channel + and + requires the -Z unstable-options flag to enable (see #10098 + ). -h, --help Prints help information. diff --git a/src/doc/man/generated_txt/cargo-build.txt b/src/doc/man/generated_txt/cargo-build.txt index da9284c8e4c..ff8bdb5ba72 100644 --- a/src/doc/man/generated_txt/cargo-build.txt +++ b/src/doc/man/generated_txt/cargo-build.txt @@ -318,7 +318,14 @@ OPTIONS Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as the directories - searched for discovering .cargo/config.toml, for example. + searched for discovering .cargo/config.toml, for example. This + option must appear before the command name, for example cargo -C + path/to/my-project build. + + This option is only available on the nightly channel + and + requires the -Z unstable-options flag to enable (see #10098 + ). -h, --help Prints help information. diff --git a/src/doc/man/generated_txt/cargo-check.txt b/src/doc/man/generated_txt/cargo-check.txt index 0b8a1ca6a55..bf8cb48f319 100644 --- a/src/doc/man/generated_txt/cargo-check.txt +++ b/src/doc/man/generated_txt/cargo-check.txt @@ -303,7 +303,14 @@ OPTIONS Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as the directories - searched for discovering .cargo/config.toml, for example. + searched for discovering .cargo/config.toml, for example. This + option must appear before the command name, for example cargo -C + path/to/my-project build. + + This option is only available on the nightly channel + and + requires the -Z unstable-options flag to enable (see #10098 + ). -h, --help Prints help information. diff --git a/src/doc/man/generated_txt/cargo-clean.txt b/src/doc/man/generated_txt/cargo-clean.txt index c0fd956bbb7..33cebb719db 100644 --- a/src/doc/man/generated_txt/cargo-clean.txt +++ b/src/doc/man/generated_txt/cargo-clean.txt @@ -132,7 +132,14 @@ OPTIONS Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as the directories - searched for discovering .cargo/config.toml, for example. + searched for discovering .cargo/config.toml, for example. This + option must appear before the command name, for example cargo -C + path/to/my-project build. + + This option is only available on the nightly channel + and + requires the -Z unstable-options flag to enable (see #10098 + ). -h, --help Prints help information. diff --git a/src/doc/man/generated_txt/cargo-doc.txt b/src/doc/man/generated_txt/cargo-doc.txt index b6805929262..82503282685 100644 --- a/src/doc/man/generated_txt/cargo-doc.txt +++ b/src/doc/man/generated_txt/cargo-doc.txt @@ -274,7 +274,14 @@ OPTIONS Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as the directories - searched for discovering .cargo/config.toml, for example. + searched for discovering .cargo/config.toml, for example. This + option must appear before the command name, for example cargo -C + path/to/my-project build. + + This option is only available on the nightly channel + and + requires the -Z unstable-options flag to enable (see #10098 + ). -h, --help Prints help information. diff --git a/src/doc/man/generated_txt/cargo-fetch.txt b/src/doc/man/generated_txt/cargo-fetch.txt index ba866323407..cbd3169c313 100644 --- a/src/doc/man/generated_txt/cargo-fetch.txt +++ b/src/doc/man/generated_txt/cargo-fetch.txt @@ -117,7 +117,14 @@ OPTIONS Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as the directories - searched for discovering .cargo/config.toml, for example. + searched for discovering .cargo/config.toml, for example. This + option must appear before the command name, for example cargo -C + path/to/my-project build. + + This option is only available on the nightly channel + and + requires the -Z unstable-options flag to enable (see #10098 + ). -h, --help Prints help information. diff --git a/src/doc/man/generated_txt/cargo-fix.txt b/src/doc/man/generated_txt/cargo-fix.txt index 2b94fdbcb85..87d72ad381a 100644 --- a/src/doc/man/generated_txt/cargo-fix.txt +++ b/src/doc/man/generated_txt/cargo-fix.txt @@ -376,7 +376,14 @@ OPTIONS Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as the directories - searched for discovering .cargo/config.toml, for example. + searched for discovering .cargo/config.toml, for example. This + option must appear before the command name, for example cargo -C + path/to/my-project build. + + This option is only available on the nightly channel + and + requires the -Z unstable-options flag to enable (see #10098 + ). -h, --help Prints help information. diff --git a/src/doc/man/generated_txt/cargo-generate-lockfile.txt b/src/doc/man/generated_txt/cargo-generate-lockfile.txt index ef15886da95..17f2a37ab7c 100644 --- a/src/doc/man/generated_txt/cargo-generate-lockfile.txt +++ b/src/doc/man/generated_txt/cargo-generate-lockfile.txt @@ -92,7 +92,14 @@ OPTIONS Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as the directories - searched for discovering .cargo/config.toml, for example. + searched for discovering .cargo/config.toml, for example. This + option must appear before the command name, for example cargo -C + path/to/my-project build. + + This option is only available on the nightly channel + and + requires the -Z unstable-options flag to enable (see #10098 + ). -h, --help Prints help information. diff --git a/src/doc/man/generated_txt/cargo-init.txt b/src/doc/man/generated_txt/cargo-init.txt index 6d1b5ccd653..678024881bb 100644 --- a/src/doc/man/generated_txt/cargo-init.txt +++ b/src/doc/man/generated_txt/cargo-init.txt @@ -100,7 +100,14 @@ OPTIONS Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as the directories - searched for discovering .cargo/config.toml, for example. + searched for discovering .cargo/config.toml, for example. This + option must appear before the command name, for example cargo -C + path/to/my-project build. + + This option is only available on the nightly channel + and + requires the -Z unstable-options flag to enable (see #10098 + ). -h, --help Prints help information. diff --git a/src/doc/man/generated_txt/cargo-install.txt b/src/doc/man/generated_txt/cargo-install.txt index a285a1b6890..a29cdcd46ec 100644 --- a/src/doc/man/generated_txt/cargo-install.txt +++ b/src/doc/man/generated_txt/cargo-install.txt @@ -215,6 +215,11 @@ OPTIONS for more details on profiles. + --ignore-rust-version + Install the target even if the selected Rust compiler is older than + the required Rust version as configured in the project’s + rust-version field. + --timings=fmts Output information how long each compilation takes, and track concurrency information over time. Accepts an optional @@ -350,7 +355,14 @@ OPTIONS Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as the directories - searched for discovering .cargo/config.toml, for example. + searched for discovering .cargo/config.toml, for example. This + option must appear before the command name, for example cargo -C + path/to/my-project build. + + This option is only available on the nightly channel + and + requires the -Z unstable-options flag to enable (see #10098 + ). -h, --help Prints help information. diff --git a/src/doc/man/generated_txt/cargo-locate-project.txt b/src/doc/man/generated_txt/cargo-locate-project.txt index bc11890e998..68a563bfbec 100644 --- a/src/doc/man/generated_txt/cargo-locate-project.txt +++ b/src/doc/man/generated_txt/cargo-locate-project.txt @@ -83,7 +83,14 @@ OPTIONS Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as the directories - searched for discovering .cargo/config.toml, for example. + searched for discovering .cargo/config.toml, for example. This + option must appear before the command name, for example cargo -C + path/to/my-project build. + + This option is only available on the nightly channel + and + requires the -Z unstable-options flag to enable (see #10098 + ). -h, --help Prints help information. diff --git a/src/doc/man/generated_txt/cargo-login.txt b/src/doc/man/generated_txt/cargo-login.txt index da61f370256..cce8efcfbdd 100644 --- a/src/doc/man/generated_txt/cargo-login.txt +++ b/src/doc/man/generated_txt/cargo-login.txt @@ -75,7 +75,14 @@ OPTIONS Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as the directories - searched for discovering .cargo/config.toml, for example. + searched for discovering .cargo/config.toml, for example. This + option must appear before the command name, for example cargo -C + path/to/my-project build. + + This option is only available on the nightly channel + and + requires the -Z unstable-options flag to enable (see #10098 + ). -h, --help Prints help information. @@ -100,5 +107,5 @@ EXAMPLES cargo login SEE ALSO - cargo(1), cargo-publish(1) + cargo(1), cargo-logout(1), cargo-publish(1) diff --git a/src/doc/man/generated_txt/cargo-logout.txt b/src/doc/man/generated_txt/cargo-logout.txt new file mode 100644 index 00000000000..db21a39b4d6 --- /dev/null +++ b/src/doc/man/generated_txt/cargo-logout.txt @@ -0,0 +1,115 @@ +CARGO-LOGOUT(1) + +NAME + cargo-logout — Remove an API token from the registry locally + +SYNOPSIS + cargo logout [options] + +DESCRIPTION + This command will remove the API token from the local credential + storage. Credentials are stored in $CARGO_HOME/credentials.toml where + $CARGO_HOME defaults to .cargo in your home directory. + + If --registry is not specified, then the credentials for the default + registry will be removed (configured by registry.default + , + which defaults to ). + + This will not revoke the token on the server. If you need to revoke the + token, visit the registry website and follow its instructions (see + to revoke the token for ). + +OPTIONS + Logout Options + --registry registry + Name of the registry to use. Registry names are defined in Cargo + config files + . If not + specified, the default registry is used, which is defined by the + registry.default config key which defaults to crates-io. + + Display Options + -v, --verbose + Use verbose output. May be specified twice for “very verbose” + output which includes extra output such as dependency warnings and + build script output. May also be specified with the term.verbose + config value + . + + -q, --quiet + Do not print cargo log messages. May also be specified with the + term.quiet config value + . + + --color when + Control when colored output is used. Valid values: + + o auto (default): Automatically detect if color support is + available on the terminal. + + o always: Always display colors. + + o never: Never display colors. + + May also be specified with the term.color config value + . + + Common Options + +toolchain + If Cargo has been installed with rustup, and the first argument to + cargo begins with +, it will be interpreted as a rustup toolchain + name (such as +stable or +nightly). See the rustup documentation + for more + information about how toolchain overrides work. + + --config KEY=VALUE or PATH + Overrides a Cargo configuration value. The argument should be in + TOML syntax of KEY=VALUE, or provided as a path to an extra + configuration file. This flag may be specified multiple times. See + the command-line overrides section + + for more information. + + -C PATH + Changes the current working directory before executing any specified + operations. This affects things like where cargo looks by default + for the project manifest (Cargo.toml), as well as the directories + searched for discovering .cargo/config.toml, for example. This + option must appear before the command name, for example cargo -C + path/to/my-project build. + + This option is only available on the nightly channel + and + requires the -Z unstable-options flag to enable (see #10098 + ). + + -h, --help + Prints help information. + + -Z flag + Unstable (nightly-only) flags to Cargo. Run cargo -Z help for + details. + +ENVIRONMENT + See the reference + + for details on environment variables that Cargo reads. + +EXIT STATUS + o 0: Cargo succeeded. + + o 101: Cargo failed to complete. + +EXAMPLES + 1. Remove the default registry token: + + cargo logout + + 2. Remove the token for a specific registry: + + cargo logout --registry my-registry + +SEE ALSO + cargo(1), cargo-login(1) + diff --git a/src/doc/man/generated_txt/cargo-metadata.txt b/src/doc/man/generated_txt/cargo-metadata.txt index 64f1e74906e..be8bed7c66f 100644 --- a/src/doc/man/generated_txt/cargo-metadata.txt +++ b/src/doc/man/generated_txt/cargo-metadata.txt @@ -405,7 +405,14 @@ OPTIONS Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as the directories - searched for discovering .cargo/config.toml, for example. + searched for discovering .cargo/config.toml, for example. This + option must appear before the command name, for example cargo -C + path/to/my-project build. + + This option is only available on the nightly channel + and + requires the -Z unstable-options flag to enable (see #10098 + ). -h, --help Prints help information. diff --git a/src/doc/man/generated_txt/cargo-new.txt b/src/doc/man/generated_txt/cargo-new.txt index 6590aaf0e02..5d2c61b48b3 100644 --- a/src/doc/man/generated_txt/cargo-new.txt +++ b/src/doc/man/generated_txt/cargo-new.txt @@ -95,7 +95,14 @@ OPTIONS Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as the directories - searched for discovering .cargo/config.toml, for example. + searched for discovering .cargo/config.toml, for example. This + option must appear before the command name, for example cargo -C + path/to/my-project build. + + This option is only available on the nightly channel + and + requires the -Z unstable-options flag to enable (see #10098 + ). -h, --help Prints help information. diff --git a/src/doc/man/generated_txt/cargo-owner.txt b/src/doc/man/generated_txt/cargo-owner.txt index 18a52503a4b..a77975da008 100644 --- a/src/doc/man/generated_txt/cargo-owner.txt +++ b/src/doc/man/generated_txt/cargo-owner.txt @@ -102,7 +102,14 @@ OPTIONS Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as the directories - searched for discovering .cargo/config.toml, for example. + searched for discovering .cargo/config.toml, for example. This + option must appear before the command name, for example cargo -C + path/to/my-project build. + + This option is only available on the nightly channel + and + requires the -Z unstable-options flag to enable (see #10098 + ). -h, --help Prints help information. diff --git a/src/doc/man/generated_txt/cargo-package.txt b/src/doc/man/generated_txt/cargo-package.txt index c5e33a5d2e9..960e0248e96 100644 --- a/src/doc/man/generated_txt/cargo-package.txt +++ b/src/doc/man/generated_txt/cargo-package.txt @@ -244,7 +244,14 @@ OPTIONS Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as the directories - searched for discovering .cargo/config.toml, for example. + searched for discovering .cargo/config.toml, for example. This + option must appear before the command name, for example cargo -C + path/to/my-project build. + + This option is only available on the nightly channel + and + requires the -Z unstable-options flag to enable (see #10098 + ). -h, --help Prints help information. diff --git a/src/doc/man/generated_txt/cargo-pkgid.txt b/src/doc/man/generated_txt/cargo-pkgid.txt index 4564ade6bfc..e2df3fd9893 100644 --- a/src/doc/man/generated_txt/cargo-pkgid.txt +++ b/src/doc/man/generated_txt/cargo-pkgid.txt @@ -36,7 +36,7 @@ DESCRIPTION | | https://github.com/rust-lang/crates.io-index#bitflags | +-----------------+--------------------------------------------------+ | | | - | url#name:version | https://github.com/rust-lang/cargo#crates-io@0.21.0 | + | url#name@version | https://github.com/rust-lang/cargo#crates-io@0.21.0 | +-----------------+--------------------------------------------------+ OPTIONS @@ -122,7 +122,14 @@ OPTIONS Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as the directories - searched for discovering .cargo/config.toml, for example. + searched for discovering .cargo/config.toml, for example. This + option must appear before the command name, for example cargo -C + path/to/my-project build. + + This option is only available on the nightly channel + and + requires the -Z unstable-options flag to enable (see #10098 + ). -h, --help Prints help information. diff --git a/src/doc/man/generated_txt/cargo-publish.txt b/src/doc/man/generated_txt/cargo-publish.txt index e356c2d73ee..d35172ad739 100644 --- a/src/doc/man/generated_txt/cargo-publish.txt +++ b/src/doc/man/generated_txt/cargo-publish.txt @@ -210,7 +210,14 @@ OPTIONS Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as the directories - searched for discovering .cargo/config.toml, for example. + searched for discovering .cargo/config.toml, for example. This + option must appear before the command name, for example cargo -C + path/to/my-project build. + + This option is only available on the nightly channel + and + requires the -Z unstable-options flag to enable (see #10098 + ). -h, --help Prints help information. diff --git a/src/doc/man/generated_txt/cargo-remove.txt b/src/doc/man/generated_txt/cargo-remove.txt index bac7483188f..53451c28952 100644 --- a/src/doc/man/generated_txt/cargo-remove.txt +++ b/src/doc/man/generated_txt/cargo-remove.txt @@ -23,6 +23,9 @@ OPTIONS Remove as a dependency to the given target platform . + To avoid unexpected shell expansions, you may use quotes around each + target, e.g., --target 'cfg(unix)'. + Miscellaneous Options --dry-run Don’t actually write to the manifest. @@ -108,7 +111,14 @@ OPTIONS Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as the directories - searched for discovering .cargo/config.toml, for example. + searched for discovering .cargo/config.toml, for example. This + option must appear before the command name, for example cargo -C + path/to/my-project build. + + This option is only available on the nightly channel + and + requires the -Z unstable-options flag to enable (see #10098 + ). -h, --help Prints help information. diff --git a/src/doc/man/generated_txt/cargo-run.txt b/src/doc/man/generated_txt/cargo-run.txt index 4512bb0c97f..f6782be1169 100644 --- a/src/doc/man/generated_txt/cargo-run.txt +++ b/src/doc/man/generated_txt/cargo-run.txt @@ -13,6 +13,10 @@ DESCRIPTION to run. If you’re passing arguments to both Cargo and the binary, the ones after -- go to the binary, the ones before go to Cargo. + Unlike cargo-test(1) and cargo-bench(1), cargo run sets the working + directory of the binary executed to the current working directory, same + as if it was executed in the shell directly. + OPTIONS Package Selection By default, the package in the current working directory is selected. @@ -218,7 +222,14 @@ OPTIONS Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as the directories - searched for discovering .cargo/config.toml, for example. + searched for discovering .cargo/config.toml, for example. This + option must appear before the command name, for example cargo -C + path/to/my-project build. + + This option is only available on the nightly channel + and + requires the -Z unstable-options flag to enable (see #10098 + ). -h, --help Prints help information. diff --git a/src/doc/man/generated_txt/cargo-rustc.txt b/src/doc/man/generated_txt/cargo-rustc.txt index f0a6edb12c6..cc4241f93a0 100644 --- a/src/doc/man/generated_txt/cargo-rustc.txt +++ b/src/doc/man/generated_txt/cargo-rustc.txt @@ -196,7 +196,7 @@ OPTIONS --crate-type crate-type Build for the given crate type. This flag accepts a comma-separated list of 1 or more crate types, of which the allowed values are the - same as crate-type field in the manifest for configurating a Cargo + same as crate-type field in the manifest for configuring a Cargo target. See crate-type field for possible values. @@ -320,7 +320,14 @@ OPTIONS Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as the directories - searched for discovering .cargo/config.toml, for example. + searched for discovering .cargo/config.toml, for example. This + option must appear before the command name, for example cargo -C + path/to/my-project build. + + This option is only available on the nightly channel + and + requires the -Z unstable-options flag to enable (see #10098 + ). -h, --help Prints help information. diff --git a/src/doc/man/generated_txt/cargo-rustdoc.txt b/src/doc/man/generated_txt/cargo-rustdoc.txt index dcd9c566efc..6a32a6b6e6a 100644 --- a/src/doc/man/generated_txt/cargo-rustdoc.txt +++ b/src/doc/man/generated_txt/cargo-rustdoc.txt @@ -290,7 +290,14 @@ OPTIONS Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as the directories - searched for discovering .cargo/config.toml, for example. + searched for discovering .cargo/config.toml, for example. This + option must appear before the command name, for example cargo -C + path/to/my-project build. + + This option is only available on the nightly channel + and + requires the -Z unstable-options flag to enable (see #10098 + ). -h, --help Prints help information. diff --git a/src/doc/man/generated_txt/cargo-search.txt b/src/doc/man/generated_txt/cargo-search.txt index 42381cf4a45..74bbda9f706 100644 --- a/src/doc/man/generated_txt/cargo-search.txt +++ b/src/doc/man/generated_txt/cargo-search.txt @@ -72,7 +72,14 @@ OPTIONS Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as the directories - searched for discovering .cargo/config.toml, for example. + searched for discovering .cargo/config.toml, for example. This + option must appear before the command name, for example cargo -C + path/to/my-project build. + + This option is only available on the nightly channel + and + requires the -Z unstable-options flag to enable (see #10098 + ). -h, --help Prints help information. diff --git a/src/doc/man/generated_txt/cargo-test.txt b/src/doc/man/generated_txt/cargo-test.txt index bb17deb9d85..3f4ed607235 100644 --- a/src/doc/man/generated_txt/cargo-test.txt +++ b/src/doc/man/generated_txt/cargo-test.txt @@ -52,6 +52,13 @@ DESCRIPTION See the rustdoc book for more information on writing doc tests. + Working directory of tests + The working directory of every test is set to the root directory of the + package the test belongs to. Setting the working directory of tests to + the package’s root directory makes it possible for tests to reliably + access the package’s files using relative paths, regardless from where + cargo test was executed from. + OPTIONS Test Options --no-run @@ -387,7 +394,14 @@ OPTIONS Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as the directories - searched for discovering .cargo/config.toml, for example. + searched for discovering .cargo/config.toml, for example. This + option must appear before the command name, for example cargo -C + path/to/my-project build. + + This option is only available on the nightly channel + and + requires the -Z unstable-options flag to enable (see #10098 + ). -h, --help Prints help information. diff --git a/src/doc/man/generated_txt/cargo-tree.txt b/src/doc/man/generated_txt/cargo-tree.txt index b8e5c98f557..5b81f0aa1f3 100644 --- a/src/doc/man/generated_txt/cargo-tree.txt +++ b/src/doc/man/generated_txt/cargo-tree.txt @@ -303,7 +303,14 @@ OPTIONS Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as the directories - searched for discovering .cargo/config.toml, for example. + searched for discovering .cargo/config.toml, for example. This + option must appear before the command name, for example cargo -C + path/to/my-project build. + + This option is only available on the nightly channel + and + requires the -Z unstable-options flag to enable (see #10098 + ). -h, --help Prints help information. diff --git a/src/doc/man/generated_txt/cargo-uninstall.txt b/src/doc/man/generated_txt/cargo-uninstall.txt index dcf3d55f75a..53b21cde892 100644 --- a/src/doc/man/generated_txt/cargo-uninstall.txt +++ b/src/doc/man/generated_txt/cargo-uninstall.txt @@ -84,7 +84,14 @@ OPTIONS Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as the directories - searched for discovering .cargo/config.toml, for example. + searched for discovering .cargo/config.toml, for example. This + option must appear before the command name, for example cargo -C + path/to/my-project build. + + This option is only available on the nightly channel + and + requires the -Z unstable-options flag to enable (see #10098 + ). -h, --help Prints help information. diff --git a/src/doc/man/generated_txt/cargo-update.txt b/src/doc/man/generated_txt/cargo-update.txt index a6cec24e30b..fb662c3890f 100644 --- a/src/doc/man/generated_txt/cargo-update.txt +++ b/src/doc/man/generated_txt/cargo-update.txt @@ -122,7 +122,14 @@ OPTIONS Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as the directories - searched for discovering .cargo/config.toml, for example. + searched for discovering .cargo/config.toml, for example. This + option must appear before the command name, for example cargo -C + path/to/my-project build. + + This option is only available on the nightly channel + and + requires the -Z unstable-options flag to enable (see #10098 + ). -h, --help Prints help information. diff --git a/src/doc/man/generated_txt/cargo-vendor.txt b/src/doc/man/generated_txt/cargo-vendor.txt index 0525e463b35..c325b753423 100644 --- a/src/doc/man/generated_txt/cargo-vendor.txt +++ b/src/doc/man/generated_txt/cargo-vendor.txt @@ -118,7 +118,14 @@ OPTIONS Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as the directories - searched for discovering .cargo/config.toml, for example. + searched for discovering .cargo/config.toml, for example. This + option must appear before the command name, for example cargo -C + path/to/my-project build. + + This option is only available on the nightly channel + and + requires the -Z unstable-options flag to enable (see #10098 + ). -h, --help Prints help information. diff --git a/src/doc/man/generated_txt/cargo-verify-project.txt b/src/doc/man/generated_txt/cargo-verify-project.txt index f641ce911d0..e0e7a4d2b28 100644 --- a/src/doc/man/generated_txt/cargo-verify-project.txt +++ b/src/doc/man/generated_txt/cargo-verify-project.txt @@ -95,7 +95,14 @@ OPTIONS Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as the directories - searched for discovering .cargo/config.toml, for example. + searched for discovering .cargo/config.toml, for example. This + option must appear before the command name, for example cargo -C + path/to/my-project build. + + This option is only available on the nightly channel + and + requires the -Z unstable-options flag to enable (see #10098 + ). -h, --help Prints help information. diff --git a/src/doc/man/generated_txt/cargo-yank.txt b/src/doc/man/generated_txt/cargo-yank.txt index e07e52626fe..376d7373c6b 100644 --- a/src/doc/man/generated_txt/cargo-yank.txt +++ b/src/doc/man/generated_txt/cargo-yank.txt @@ -99,7 +99,14 @@ OPTIONS Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as the directories - searched for discovering .cargo/config.toml, for example. + searched for discovering .cargo/config.toml, for example. This + option must appear before the command name, for example cargo -C + path/to/my-project build. + + This option is only available on the nightly channel + and + requires the -Z unstable-options flag to enable (see #10098 + ). -h, --help Prints help information. diff --git a/src/doc/man/generated_txt/cargo.txt b/src/doc/man/generated_txt/cargo.txt index 9ebf6ef2150..5c0762e6038 100644 --- a/src/doc/man/generated_txt/cargo.txt +++ b/src/doc/man/generated_txt/cargo.txt @@ -95,6 +95,9 @@ COMMANDS cargo-login(1)     Save an API token from the registry locally. + cargo-logout(1) +     Remove an API token from the registry locally. + cargo-owner(1)     Manage the owners of a crate on the registry. @@ -201,7 +204,14 @@ OPTIONS Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as the directories - searched for discovering .cargo/config.toml, for example. + searched for discovering .cargo/config.toml, for example. This + option must appear before the command name, for example cargo -C + path/to/my-project build. + + This option is only available on the nightly channel + and + requires the -Z unstable-options flag to enable (see #10098 + ). -h, --help Prints help information. diff --git a/src/doc/man/includes/section-options-common.md b/src/doc/man/includes/section-options-common.md index db09f977fd0..5a41169d6f0 100644 --- a/src/doc/man/includes/section-options-common.md +++ b/src/doc/man/includes/section-options-common.md @@ -19,7 +19,13 @@ See the [command-line overrides section](../reference/config.html#command-line-o {{#option "`-C` _PATH_"}} Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (`Cargo.toml`), as well as -the directories searched for discovering `.cargo/config.toml`, for example. +the directories searched for discovering `.cargo/config.toml`, for example. This option must +appear before the command name, for example `cargo -C path/to/my-project build`. + +This option is only available on the [nightly +channel](https://doc.rust-lang.org/book/appendix-07-nightly-rust.html) and +requires the `-Z unstable-options` flag to enable (see +[#10098](https://github.com/rust-lang/cargo/issues/10098)). {{/option}} {{#option "`-h`" "`--help`"}} diff --git a/src/doc/src/SUMMARY.md b/src/doc/src/SUMMARY.md index c44bc864e53..27393655985 100644 --- a/src/doc/src/SUMMARY.md +++ b/src/doc/src/SUMMARY.md @@ -82,6 +82,7 @@ * [cargo uninstall](commands/cargo-uninstall.md) * [Publishing Commands](commands/publishing-commands.md) * [cargo login](commands/cargo-login.md) + * [cargo logout](commands/cargo-logout.md) * [cargo owner](commands/cargo-owner.md) * [cargo package](commands/cargo-package.md) * [cargo publish](commands/cargo-publish.md) diff --git a/src/doc/src/commands/cargo-add.md b/src/doc/src/commands/cargo-add.md index 1f575b552fc..89c1cc6f1ea 100644 --- a/src/doc/src/commands/cargo-add.md +++ b/src/doc/src/commands/cargo-add.md @@ -86,10 +86,8 @@ which is defined by the registry.default config key which defaults

--target target
-
Add as a dependency to the given target platform.
- - - +
Add as a dependency to the given target platform.

+

To avoid unexpected shell expansions, you may use quotes around each target, e.g., --target 'cfg(unix)'.

@@ -227,7 +225,12 @@ See the command-line o
-C PATH
Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as -the directories searched for discovering .cargo/config.toml, for example.
+the directories searched for discovering .cargo/config.toml, for example. This option must +appear before the command name, for example cargo -C path/to/my-project build.

+

This option is only available on the nightly +channel and +requires the -Z unstable-options flag to enable (see +#10098).

-h
@@ -272,5 +275,9 @@ details on environment variables that Cargo reads. cargo add serde serde_json -F serde/derive +5. Add `windows` as a platform specific dependency on `cfg(windows)` + + cargo add windows --target 'cfg(windows)' + ## SEE ALSO [cargo(1)](cargo.html), [cargo-remove(1)](cargo-remove.html) diff --git a/src/doc/src/commands/cargo-bench.md b/src/doc/src/commands/cargo-bench.md index e1858c402c2..e7e9b36fb8f 100644 --- a/src/doc/src/commands/cargo-bench.md +++ b/src/doc/src/commands/cargo-bench.md @@ -56,6 +56,14 @@ debugger. [`bench` profile]: ../reference/profiles.html#bench +### Working directory of benchmarks + +The working directory of every benchmark is set to the root directory of the +package the benchmark belongs to. +Setting the working directory of benchmarks to the package's root directory +makes it possible for benchmarks to reliably access the package's files using +relative paths, regardless from where `cargo bench` was executed from. + ## OPTIONS ### Benchmark Options @@ -430,7 +438,12 @@ See the command-line o
-C PATH
Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as -the directories searched for discovering .cargo/config.toml, for example.
+the directories searched for discovering .cargo/config.toml, for example. This option must +appear before the command name, for example cargo -C path/to/my-project build.

+

This option is only available on the nightly +channel and +requires the -Z unstable-options flag to enable (see +#10098).

-h
diff --git a/src/doc/src/commands/cargo-build.md b/src/doc/src/commands/cargo-build.md index 6b60097aebf..6e3cf157acb 100644 --- a/src/doc/src/commands/cargo-build.md +++ b/src/doc/src/commands/cargo-build.md @@ -374,7 +374,12 @@ See the command-line o
-C PATH
Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as -the directories searched for discovering .cargo/config.toml, for example.
+the directories searched for discovering .cargo/config.toml, for example. This option must +appear before the command name, for example cargo -C path/to/my-project build.

+

This option is only available on the nightly +channel and +requires the -Z unstable-options flag to enable (see +#10098).

-h
diff --git a/src/doc/src/commands/cargo-check.md b/src/doc/src/commands/cargo-check.md index 18b0e2b5ee7..2070293acca 100644 --- a/src/doc/src/commands/cargo-check.md +++ b/src/doc/src/commands/cargo-check.md @@ -355,7 +355,12 @@ See the command-line o
-C PATH
Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as -the directories searched for discovering .cargo/config.toml, for example.
+the directories searched for discovering .cargo/config.toml, for example. This option must +appear before the command name, for example cargo -C path/to/my-project build.

+

This option is only available on the nightly +channel and +requires the -Z unstable-options flag to enable (see +#10098).

-h
diff --git a/src/doc/src/commands/cargo-clean.md b/src/doc/src/commands/cargo-clean.md index 369837920fe..bdbcb86d4a9 100644 --- a/src/doc/src/commands/cargo-clean.md +++ b/src/doc/src/commands/cargo-clean.md @@ -159,7 +159,12 @@ See the command-line o
-C PATH
Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as -the directories searched for discovering .cargo/config.toml, for example.
+the directories searched for discovering .cargo/config.toml, for example. This option must +appear before the command name, for example cargo -C path/to/my-project build.

+

This option is only available on the nightly +channel and +requires the -Z unstable-options flag to enable (see +#10098).

-h
diff --git a/src/doc/src/commands/cargo-doc.md b/src/doc/src/commands/cargo-doc.md index f1150f56701..e0e5c8ed219 100644 --- a/src/doc/src/commands/cargo-doc.md +++ b/src/doc/src/commands/cargo-doc.md @@ -329,7 +329,12 @@ See the command-line o
-C PATH
Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as -the directories searched for discovering .cargo/config.toml, for example.
+the directories searched for discovering .cargo/config.toml, for example. This option must +appear before the command name, for example cargo -C path/to/my-project build.

+

This option is only available on the nightly +channel and +requires the -Z unstable-options flag to enable (see +#10098).

-h
diff --git a/src/doc/src/commands/cargo-fetch.md b/src/doc/src/commands/cargo-fetch.md index b461619f64b..e6a46079551 100644 --- a/src/doc/src/commands/cargo-fetch.md +++ b/src/doc/src/commands/cargo-fetch.md @@ -133,7 +133,12 @@ See the command-line o
-C PATH
Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as -the directories searched for discovering .cargo/config.toml, for example.
+the directories searched for discovering .cargo/config.toml, for example. This option must +appear before the command name, for example cargo -C path/to/my-project build.

+

This option is only available on the nightly +channel and +requires the -Z unstable-options flag to enable (see +#10098).

-h
diff --git a/src/doc/src/commands/cargo-fix.md b/src/doc/src/commands/cargo-fix.md index 1b45fc27ff2..1b9ec6a8526 100644 --- a/src/doc/src/commands/cargo-fix.md +++ b/src/doc/src/commands/cargo-fix.md @@ -435,7 +435,12 @@ See the command-line o
-C PATH
Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as -the directories searched for discovering .cargo/config.toml, for example.
+the directories searched for discovering .cargo/config.toml, for example. This option must +appear before the command name, for example cargo -C path/to/my-project build.

+

This option is only available on the nightly +channel and +requires the -Z unstable-options flag to enable (see +#10098).

-h
diff --git a/src/doc/src/commands/cargo-generate-lockfile.md b/src/doc/src/commands/cargo-generate-lockfile.md index 65c0591b5af..eb8d2e30e13 100644 --- a/src/doc/src/commands/cargo-generate-lockfile.md +++ b/src/doc/src/commands/cargo-generate-lockfile.md @@ -107,7 +107,12 @@ See the command-line o
-C PATH
Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as -the directories searched for discovering .cargo/config.toml, for example.
+the directories searched for discovering .cargo/config.toml, for example. This option must +appear before the command name, for example cargo -C path/to/my-project build.

+

This option is only available on the nightly +channel and +requires the -Z unstable-options flag to enable (see +#10098).

-h
diff --git a/src/doc/src/commands/cargo-init.md b/src/doc/src/commands/cargo-init.md index 83f681fdc24..c0cf34b517c 100644 --- a/src/doc/src/commands/cargo-init.md +++ b/src/doc/src/commands/cargo-init.md @@ -120,7 +120,12 @@ See the command-line o
-C PATH
Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as -the directories searched for discovering .cargo/config.toml, for example.
+the directories searched for discovering .cargo/config.toml, for example. This option must +appear before the command name, for example cargo -C path/to/my-project build.

+

This option is only available on the nightly +channel and +requires the -Z unstable-options flag to enable (see +#10098).

-h
diff --git a/src/doc/src/commands/cargo-install.md b/src/doc/src/commands/cargo-install.md index 1601872ed8b..99697c156c3 100644 --- a/src/doc/src/commands/cargo-install.md +++ b/src/doc/src/commands/cargo-install.md @@ -248,6 +248,12 @@ See the the reference for more details +
--ignore-rust-version
+
Install the target even if the selected Rust compiler is older than the +required Rust version as configured in the project’s rust-version field.
+ + +
--timings=fmts
Output information how long each compilation takes, and track concurrency information over time. Accepts an optional comma-separated list of output @@ -396,7 +402,12 @@ See the command-line o
-C PATH
Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as -the directories searched for discovering .cargo/config.toml, for example.
+the directories searched for discovering .cargo/config.toml, for example. This option must +appear before the command name, for example cargo -C path/to/my-project build.

+

This option is only available on the nightly +channel and +requires the -Z unstable-options flag to enable (see +#10098).

-h
diff --git a/src/doc/src/commands/cargo-locate-project.md b/src/doc/src/commands/cargo-locate-project.md index efecd255c3a..00491b398c6 100644 --- a/src/doc/src/commands/cargo-locate-project.md +++ b/src/doc/src/commands/cargo-locate-project.md @@ -102,7 +102,12 @@ See the command-line o
-C PATH
Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as -the directories searched for discovering .cargo/config.toml, for example.
+the directories searched for discovering .cargo/config.toml, for example. This option must +appear before the command name, for example cargo -C path/to/my-project build.

+

This option is only available on the nightly +channel and +requires the -Z unstable-options flag to enable (see +#10098).

-h
diff --git a/src/doc/src/commands/cargo-login.md b/src/doc/src/commands/cargo-login.md index 63a885e9b59..e738dca0626 100644 --- a/src/doc/src/commands/cargo-login.md +++ b/src/doc/src/commands/cargo-login.md @@ -88,7 +88,12 @@ See the command-line o
-C PATH
Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as -the directories searched for discovering .cargo/config.toml, for example.
+the directories searched for discovering .cargo/config.toml, for example. This option must +appear before the command name, for example cargo -C path/to/my-project build.

+

This option is only available on the nightly +channel and +requires the -Z unstable-options flag to enable (see +#10098).

-h
@@ -122,4 +127,4 @@ details on environment variables that Cargo reads. cargo login ## SEE ALSO -[cargo(1)](cargo.html), [cargo-publish(1)](cargo-publish.html) +[cargo(1)](cargo.html), [cargo-logout(1)](cargo-logout.html), [cargo-publish(1)](cargo-publish.html) diff --git a/src/doc/src/commands/cargo-logout.md b/src/doc/src/commands/cargo-logout.md new file mode 100644 index 00000000000..16e393b0219 --- /dev/null +++ b/src/doc/src/commands/cargo-logout.md @@ -0,0 +1,136 @@ +# cargo-logout(1) + +## NAME + +cargo-logout --- Remove an API token from the registry locally + +## SYNOPSIS + +`cargo logout` [_options_] + +## DESCRIPTION + +This command will remove the API token from the local credential storage. +Credentials are stored in `$CARGO_HOME/credentials.toml` where `$CARGO_HOME` +defaults to `.cargo` in your home directory. + +If `--registry` is not specified, then the credentials for the default +registry will be removed (configured by +[`registry.default`](../reference/config.html#registrydefault), which defaults +to ). + +This will not revoke the token on the server. If you need to revoke the token, +visit the registry website and follow its instructions (see + to revoke the token for ). + +## OPTIONS + +### Logout Options + +
+
--registry registry
+
Name of the registry to use. Registry names are defined in Cargo config +files. If not specified, the default registry is used, +which is defined by the registry.default config key which defaults to +crates-io.
+ + +
+ +### Display Options + +
+
-v
+
--verbose
+
Use verbose output. May be specified twice for “very verbose” output which +includes extra output such as dependency warnings and build script output. +May also be specified with the term.verbose +config value.
+ + +
-q
+
--quiet
+
Do not print cargo log messages. +May also be specified with the term.quiet +config value.
+ + +
--color when
+
Control when colored output is used. Valid values:

+
    +
  • auto (default): Automatically detect if color support is available on the +terminal.
  • +
  • always: Always display colors.
  • +
  • never: Never display colors.
  • +
+

May also be specified with the term.color +config value.

+ + +
+ +### Common Options + +
+ +
+toolchain
+
If Cargo has been installed with rustup, and the first argument to cargo +begins with +, it will be interpreted as a rustup toolchain name (such +as +stable or +nightly). +See the rustup documentation +for more information about how toolchain overrides work.
+ + +
--config KEY=VALUE or PATH
+
Overrides a Cargo configuration value. The argument should be in TOML syntax of KEY=VALUE, +or provided as a path to an extra configuration file. This flag may be specified multiple times. +See the command-line overrides section for more information.
+ + +
-C PATH
+
Changes the current working directory before executing any specified operations. This affects +things like where cargo looks by default for the project manifest (Cargo.toml), as well as +the directories searched for discovering .cargo/config.toml, for example. This option must +appear before the command name, for example cargo -C path/to/my-project build.

+

This option is only available on the nightly +channel and +requires the -Z unstable-options flag to enable (see +#10098).

+ + +
-h
+
--help
+
Prints help information.
+ + +
-Z flag
+
Unstable (nightly-only) flags to Cargo. Run cargo -Z help for details.
+ + +
+ + +## ENVIRONMENT + +See [the reference](../reference/environment-variables.html) for +details on environment variables that Cargo reads. + + +## EXIT STATUS + +* `0`: Cargo succeeded. +* `101`: Cargo failed to complete. + + +## EXAMPLES + +1. Remove the default registry token: + + cargo logout + +2. Remove the token for a specific registry: + + cargo logout --registry my-registry + +## SEE ALSO +[cargo(1)](cargo.html), [cargo-login(1)](cargo-login.html) diff --git a/src/doc/src/commands/cargo-metadata.md b/src/doc/src/commands/cargo-metadata.md index fc89e525e05..ebde0ea101c 100644 --- a/src/doc/src/commands/cargo-metadata.md +++ b/src/doc/src/commands/cargo-metadata.md @@ -437,7 +437,12 @@ See the command-line o
-C PATH
Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as -the directories searched for discovering .cargo/config.toml, for example.
+the directories searched for discovering .cargo/config.toml, for example. This option must +appear before the command name, for example cargo -C path/to/my-project build.

+

This option is only available on the nightly +channel and +requires the -Z unstable-options flag to enable (see +#10098).

-h
diff --git a/src/doc/src/commands/cargo-new.md b/src/doc/src/commands/cargo-new.md index c08bfc05536..144b6f2ebd4 100644 --- a/src/doc/src/commands/cargo-new.md +++ b/src/doc/src/commands/cargo-new.md @@ -115,7 +115,12 @@ See the command-line o
-C PATH
Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as -the directories searched for discovering .cargo/config.toml, for example.
+the directories searched for discovering .cargo/config.toml, for example. This option must +appear before the command name, for example cargo -C path/to/my-project build.

+

This option is only available on the nightly +channel and +requires the -Z unstable-options flag to enable (see +#10098).

-h
diff --git a/src/doc/src/commands/cargo-owner.md b/src/doc/src/commands/cargo-owner.md index 17740a2ec37..caf16f4b29e 100644 --- a/src/doc/src/commands/cargo-owner.md +++ b/src/doc/src/commands/cargo-owner.md @@ -126,7 +126,12 @@ See the command-line o
-C PATH
Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as -the directories searched for discovering .cargo/config.toml, for example.
+the directories searched for discovering .cargo/config.toml, for example. This option must +appear before the command name, for example cargo -C path/to/my-project build.

+

This option is only available on the nightly +channel and +requires the -Z unstable-options flag to enable (see +#10098).

-h
diff --git a/src/doc/src/commands/cargo-package.md b/src/doc/src/commands/cargo-package.md index 827f25724fd..776b150cf84 100644 --- a/src/doc/src/commands/cargo-package.md +++ b/src/doc/src/commands/cargo-package.md @@ -292,7 +292,12 @@ See the command-line o
-C PATH
Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as -the directories searched for discovering .cargo/config.toml, for example.
+the directories searched for discovering .cargo/config.toml, for example. This option must +appear before the command name, for example cargo -C path/to/my-project build.

+

This option is only available on the nightly +channel and +requires the -Z unstable-options flag to enable (see +#10098).

-h
diff --git a/src/doc/src/commands/cargo-pkgid.md b/src/doc/src/commands/cargo-pkgid.md index b7fe6d2dfff..d7e507506b0 100644 --- a/src/doc/src/commands/cargo-pkgid.md +++ b/src/doc/src/commands/cargo-pkgid.md @@ -31,7 +31,7 @@ _name_`@`_version_ | `bitflags@1.0.4` _url_ | `https://github.com/rust-lang/cargo` _url_`#`_version_ | `https://github.com/rust-lang/cargo#0.33.0` _url_`#`_name_ | `https://github.com/rust-lang/crates.io-index#bitflags` -_url_`#`_name_`:`_version_ | `https://github.com/rust-lang/cargo#crates-io@0.21.0` +_url_`#`_name_`@`_version_ | `https://github.com/rust-lang/cargo#crates-io@0.21.0` ## OPTIONS @@ -136,7 +136,12 @@ See the command-line o
-C PATH
Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as -the directories searched for discovering .cargo/config.toml, for example.
+the directories searched for discovering .cargo/config.toml, for example. This option must +appear before the command name, for example cargo -C path/to/my-project build.

+

This option is only available on the nightly +channel and +requires the -Z unstable-options flag to enable (see +#10098).

-h
diff --git a/src/doc/src/commands/cargo-publish.md b/src/doc/src/commands/cargo-publish.md index ee49ec1f1fc..1f4fbebb8bc 100644 --- a/src/doc/src/commands/cargo-publish.md +++ b/src/doc/src/commands/cargo-publish.md @@ -258,7 +258,12 @@ See the command-line o
-C PATH
Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as -the directories searched for discovering .cargo/config.toml, for example.
+the directories searched for discovering .cargo/config.toml, for example. This option must +appear before the command name, for example cargo -C path/to/my-project build.

+

This option is only available on the nightly +channel and +requires the -Z unstable-options flag to enable (see +#10098).

-h
diff --git a/src/doc/src/commands/cargo-remove.md b/src/doc/src/commands/cargo-remove.md index 29989815a7b..571c4107503 100644 --- a/src/doc/src/commands/cargo-remove.md +++ b/src/doc/src/commands/cargo-remove.md @@ -29,7 +29,8 @@ Remove one or more dependencies from a `Cargo.toml` manifest.
--target target
-
Remove as a dependency to the given target platform.
+
Remove as a dependency to the given target platform.

+

To avoid unexpected shell expansions, you may use quotes around each target, e.g., --target 'cfg(unix)'.

@@ -143,7 +144,12 @@ See the command-line o
-C PATH
Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as -the directories searched for discovering .cargo/config.toml, for example.
+the directories searched for discovering .cargo/config.toml, for example. This option must +appear before the command name, for example cargo -C path/to/my-project build.

+

This option is only available on the nightly +channel and +requires the -Z unstable-options flag to enable (see +#10098).

-h
diff --git a/src/doc/src/commands/cargo-run.md b/src/doc/src/commands/cargo-run.md index 396c2d499dd..f6f5ec2a343 100644 --- a/src/doc/src/commands/cargo-run.md +++ b/src/doc/src/commands/cargo-run.md @@ -17,6 +17,10 @@ All the arguments following the two dashes (`--`) are passed to the binary to run. If you're passing arguments to both Cargo and the binary, the ones after `--` go to the binary, the ones before go to Cargo. +Unlike [cargo-test(1)](cargo-test.html) and [cargo-bench(1)](cargo-bench.html), `cargo run` sets the +working directory of the binary executed to the current working directory, same +as if it was executed in the shell directly. + ## OPTIONS ### Package Selection @@ -267,7 +271,12 @@ See the command-line o
-C PATH
Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as -the directories searched for discovering .cargo/config.toml, for example.
+the directories searched for discovering .cargo/config.toml, for example. This option must +appear before the command name, for example cargo -C path/to/my-project build.

+

This option is only available on the nightly +channel and +requires the -Z unstable-options flag to enable (see +#10098).

-h
diff --git a/src/doc/src/commands/cargo-rustc.md b/src/doc/src/commands/cargo-rustc.md index 43e18d70cef..946298af914 100644 --- a/src/doc/src/commands/cargo-rustc.md +++ b/src/doc/src/commands/cargo-rustc.md @@ -230,7 +230,7 @@ information about timing information.
--crate-type crate-type
Build for the given crate type. This flag accepts a comma-separated list of 1 or more crate types, of which the allowed values are the same as crate-type -field in the manifest for configurating a Cargo target. See +field in the manifest for configuring a Cargo target. See crate-type field for possible values.

If the manifest contains a list, and --crate-type is provided, @@ -368,7 +368,12 @@ See the command-line o

-C PATH
Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as -the directories searched for discovering .cargo/config.toml, for example.
+the directories searched for discovering .cargo/config.toml, for example. This option must +appear before the command name, for example cargo -C path/to/my-project build.

+

This option is only available on the nightly +channel and +requires the -Z unstable-options flag to enable (see +#10098).

-h
diff --git a/src/doc/src/commands/cargo-rustdoc.md b/src/doc/src/commands/cargo-rustdoc.md index 133fa35f26d..8467da2a32c 100644 --- a/src/doc/src/commands/cargo-rustdoc.md +++ b/src/doc/src/commands/cargo-rustdoc.md @@ -348,7 +348,12 @@ See the command-line o
-C PATH
Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as -the directories searched for discovering .cargo/config.toml, for example.
+the directories searched for discovering .cargo/config.toml, for example. This option must +appear before the command name, for example cargo -C path/to/my-project build.

+

This option is only available on the nightly +channel and +requires the -Z unstable-options flag to enable (see +#10098).

-h
diff --git a/src/doc/src/commands/cargo-search.md b/src/doc/src/commands/cargo-search.md index 6f2dedd51eb..72e2accf3ae 100644 --- a/src/doc/src/commands/cargo-search.md +++ b/src/doc/src/commands/cargo-search.md @@ -92,7 +92,12 @@ See the command-line o
-C PATH
Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as -the directories searched for discovering .cargo/config.toml, for example.
+the directories searched for discovering .cargo/config.toml, for example. This option must +appear before the command name, for example cargo -C path/to/my-project build.

+

This option is only available on the nightly +channel and +requires the -Z unstable-options flag to enable (see +#10098).

-h
diff --git a/src/doc/src/commands/cargo-test.md b/src/doc/src/commands/cargo-test.md index 0ba9dcc6148..2967d7381e9 100644 --- a/src/doc/src/commands/cargo-test.md +++ b/src/doc/src/commands/cargo-test.md @@ -57,6 +57,14 @@ and may change in the future; beware of depending on it. See the [rustdoc book](https://doc.rust-lang.org/rustdoc/) for more information on writing doc tests. +### Working directory of tests + +The working directory of every test is set to the root directory of the package +the test belongs to. +Setting the working directory of tests to the package's root directory makes it +possible for tests to reliably access the package's files using relative paths, +regardless from where `cargo test` was executed from. + ## OPTIONS ### Test Options @@ -453,7 +461,12 @@ See the command-line o
-C PATH
Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as -the directories searched for discovering .cargo/config.toml, for example.
+the directories searched for discovering .cargo/config.toml, for example. This option must +appear before the command name, for example cargo -C path/to/my-project build.

+

This option is only available on the nightly +channel and +requires the -Z unstable-options flag to enable (see +#10098).

-h
diff --git a/src/doc/src/commands/cargo-tree.md b/src/doc/src/commands/cargo-tree.md index b13d2395eb1..9a9aa9f0c2f 100644 --- a/src/doc/src/commands/cargo-tree.md +++ b/src/doc/src/commands/cargo-tree.md @@ -339,7 +339,12 @@ See the command-line o
-C PATH
Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as -the directories searched for discovering .cargo/config.toml, for example.
+the directories searched for discovering .cargo/config.toml, for example. This option must +appear before the command name, for example cargo -C path/to/my-project build.

+

This option is only available on the nightly +channel and +requires the -Z unstable-options flag to enable (see +#10098).

-h
diff --git a/src/doc/src/commands/cargo-uninstall.md b/src/doc/src/commands/cargo-uninstall.md index 2fb7490b901..e9c73b0cd56 100644 --- a/src/doc/src/commands/cargo-uninstall.md +++ b/src/doc/src/commands/cargo-uninstall.md @@ -102,7 +102,12 @@ See the command-line o
-C PATH
Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as -the directories searched for discovering .cargo/config.toml, for example.
+the directories searched for discovering .cargo/config.toml, for example. This option must +appear before the command name, for example cargo -C path/to/my-project build.

+

This option is only available on the nightly +channel and +requires the -Z unstable-options flag to enable (see +#10098).

-h
diff --git a/src/doc/src/commands/cargo-update.md b/src/doc/src/commands/cargo-update.md index 4cc8ddd64bc..3cfd6928222 100644 --- a/src/doc/src/commands/cargo-update.md +++ b/src/doc/src/commands/cargo-update.md @@ -147,7 +147,12 @@ See the command-line o
-C PATH
Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as -the directories searched for discovering .cargo/config.toml, for example.
+the directories searched for discovering .cargo/config.toml, for example. This option must +appear before the command name, for example cargo -C path/to/my-project build.

+

This option is only available on the nightly +channel and +requires the -Z unstable-options flag to enable (see +#10098).

-h
diff --git a/src/doc/src/commands/cargo-vendor.md b/src/doc/src/commands/cargo-vendor.md index 095e4418778..cf47fc25658 100644 --- a/src/doc/src/commands/cargo-vendor.md +++ b/src/doc/src/commands/cargo-vendor.md @@ -143,7 +143,12 @@ See the command-line o
-C PATH
Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as -the directories searched for discovering .cargo/config.toml, for example.
+the directories searched for discovering .cargo/config.toml, for example. This option must +appear before the command name, for example cargo -C path/to/my-project build.

+

This option is only available on the nightly +channel and +requires the -Z unstable-options flag to enable (see +#10098).

-h
diff --git a/src/doc/src/commands/cargo-verify-project.md b/src/doc/src/commands/cargo-verify-project.md index 1134b38e4df..2a1d5d950be 100644 --- a/src/doc/src/commands/cargo-verify-project.md +++ b/src/doc/src/commands/cargo-verify-project.md @@ -113,7 +113,12 @@ See the command-line o
-C PATH
Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as -the directories searched for discovering .cargo/config.toml, for example.
+the directories searched for discovering .cargo/config.toml, for example. This option must +appear before the command name, for example cargo -C path/to/my-project build.

+

This option is only available on the nightly +channel and +requires the -Z unstable-options flag to enable (see +#10098).

-h
diff --git a/src/doc/src/commands/cargo-yank.md b/src/doc/src/commands/cargo-yank.md index b1371185e02..18565d2dcb9 100644 --- a/src/doc/src/commands/cargo-yank.md +++ b/src/doc/src/commands/cargo-yank.md @@ -122,7 +122,12 @@ See the command-line o
-C PATH
Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as -the directories searched for discovering .cargo/config.toml, for example.
+the directories searched for discovering .cargo/config.toml, for example. This option must +appear before the command name, for example cargo -C path/to/my-project build.

+

This option is only available on the nightly +channel and +requires the -Z unstable-options flag to enable (see +#10098).

-h
diff --git a/src/doc/src/commands/cargo.md b/src/doc/src/commands/cargo.md index 9e3392fd410..b1b07bc7021 100644 --- a/src/doc/src/commands/cargo.md +++ b/src/doc/src/commands/cargo.md @@ -102,6 +102,9 @@ available at . [cargo-login(1)](cargo-login.html)\     Save an API token from the registry locally. +[cargo-logout(1)](cargo-logout.html)\ +    Remove an API token from the registry locally. + [cargo-owner(1)](cargo-owner.html)\     Manage the owners of a crate on the registry. @@ -230,7 +233,12 @@ See the command-line o
-C PATH
Changes the current working directory before executing any specified operations. This affects things like where cargo looks by default for the project manifest (Cargo.toml), as well as -the directories searched for discovering .cargo/config.toml, for example.
+the directories searched for discovering .cargo/config.toml, for example. This option must +appear before the command name, for example cargo -C path/to/my-project build.

+

This option is only available on the nightly +channel and +requires the -Z unstable-options flag to enable (see +#10098).

-h
diff --git a/src/doc/src/commands/publishing-commands.md b/src/doc/src/commands/publishing-commands.md index 372faada216..81d440151d3 100644 --- a/src/doc/src/commands/publishing-commands.md +++ b/src/doc/src/commands/publishing-commands.md @@ -1,5 +1,6 @@ # Publishing Commands * [cargo login](cargo-login.md) +* [cargo logout](cargo-logout.md) * [cargo owner](cargo-owner.md) * [cargo package](cargo-package.md) * [cargo publish](cargo-publish.md) diff --git a/src/doc/src/images/build-info.png b/src/doc/src/images/build-info.png new file mode 100644 index 00000000000..961b3e2f5c5 Binary files /dev/null and b/src/doc/src/images/build-info.png differ diff --git a/src/doc/src/images/build-unit-time.png b/src/doc/src/images/build-unit-time.png new file mode 100644 index 00000000000..943084c5b0c Binary files /dev/null and b/src/doc/src/images/build-unit-time.png differ diff --git a/src/doc/src/images/cargo-concurrency-over-time.png b/src/doc/src/images/cargo-concurrency-over-time.png new file mode 100644 index 00000000000..274a6167dd5 Binary files /dev/null and b/src/doc/src/images/cargo-concurrency-over-time.png differ diff --git a/src/doc/src/reference/build-scripts.md b/src/doc/src/reference/build-scripts.md index 00023b6c2bb..68e8d404f1f 100644 --- a/src/doc/src/reference/build-scripts.md +++ b/src/doc/src/reference/build-scripts.md @@ -206,7 +206,7 @@ target. #### `cargo:rustc-link-arg-benches=FLAG` The `rustc-link-arg-benches` instruction tells Cargo to pass the [`-C -link-arg=FLAG` option][link-arg] to the compiler, but only when building an benchmark +link-arg=FLAG` option][link-arg] to the compiler, but only when building a benchmark target. diff --git a/src/doc/src/reference/config.md b/src/doc/src/reference/config.md index b826beb75cc..c57a45f6759 100644 --- a/src/doc/src/reference/config.md +++ b/src/doc/src/reference/config.md @@ -110,7 +110,7 @@ user-agent = "…" # the user-agent header root = "/some/path" # `cargo install` destination directory [net] -retry = 2 # network retries +retry = 3 # network retries git-fetch-with-cli = true # use the `git` executable for git operations offline = true # do not access the network @@ -196,7 +196,7 @@ Environment variables will take precedence over TOML configuration files. Currently only integer, boolean, string and some array values are supported to be defined by environment variables. [Descriptions below](#configuration-keys) indicate which keys support environment variables and otherwise they are not -supported due to [technicial issues](https://github.com/rust-lang/cargo/issues/5416). +supported due to [technical issues](https://github.com/rust-lang/cargo/issues/5416). In addition to the system above, Cargo recognizes a few other specific [environment variables][env]. @@ -296,7 +296,7 @@ Cargo will search `PATH` for its executable. Configuration values with sensitive information are stored in the `$CARGO_HOME/credentials.toml` file. This file is automatically created and updated -by [`cargo login`]. It follows the same format as Cargo config files. +by [`cargo login`] and [`cargo logout`]. It follows the same format as Cargo config files. ```toml [registry] @@ -724,7 +724,7 @@ The `[net]` table controls networking configuration. ##### `net.retry` * Type: integer -* Default: 2 +* Default: 3 * Environment: `CARGO_NET_RETRY` Number of times to retry possibly spurious network errors. @@ -918,7 +918,7 @@ consists of a sub-table for each named registry. * Default: none * Environment: `CARGO_REGISTRIES__INDEX` -Specifies the URL of the git index for the registry. +Specifies the URL of the index for the registry. ##### `registries..token` * Type: string @@ -933,7 +933,7 @@ Can be overridden with the `--token` command-line option. ##### `registries.crates-io.protocol` * Type: string -* Default: `git` +* Default: `sparse` * Environment: `CARGO_REGISTRIES_CRATES_IO_PROTOCOL` Specifies the protocol used to access crates.io. Allowed values are `git` or `sparse`. @@ -1193,6 +1193,7 @@ Sets the width for progress bar. [`cargo bench`]: ../commands/cargo-bench.md [`cargo login`]: ../commands/cargo-login.md +[`cargo logout`]: ../commands/cargo-logout.md [`cargo doc`]: ../commands/cargo-doc.md [`cargo new`]: ../commands/cargo-new.md [`cargo publish`]: ../commands/cargo-publish.md diff --git a/src/doc/src/reference/environment-variables.md b/src/doc/src/reference/environment-variables.md index 2ed986fb5d2..95c4c87fbb9 100644 --- a/src/doc/src/reference/environment-variables.md +++ b/src/doc/src/reference/environment-variables.md @@ -231,6 +231,7 @@ corresponding environment variable is set to the empty string, `""`. * `CARGO_PKG_RUST_VERSION` --- The Rust version from the manifest of your package. Note that this is the minimum Rust version supported by the package, not the current Rust version. +* `CARGO_PKG_README` --- Path to the README file of your package. * `CARGO_CRATE_NAME` --- The name of the crate that is currently being compiled. It is the name of the [Cargo target] with `-` converted to `_`, such as the name of the library, binary, example, integration test, or benchmark. * `CARGO_BIN_NAME` --- The name of the binary that is currently being compiled. Only set for [binaries] or binary [examples]. This name does not include any diff --git a/src/doc/src/reference/external-tools.md b/src/doc/src/reference/external-tools.md index 58f4787d188..7b5110cbe1e 100644 --- a/src/doc/src/reference/external-tools.md +++ b/src/doc/src/reference/external-tools.md @@ -105,10 +105,15 @@ structure: This property is not included if no required features are set. */ "required-features": ["feat1"], + /* Whether the target should be documented by `cargo doc`. */ + "doc": true, /* Whether or not this target has doc tests enabled, and the target is compatible with doc testing. */ "doctest": true + /* Whether or not this target should be built and run with `--test` + */ + "test": true }, /* The message emitted by the compiler. @@ -146,6 +151,7 @@ following structure: "name": "my-package", "src_path": "/path/to/my-package/src/lib.rs", "edition": "2018", + "doc": true, "doctest": true, "test": true }, diff --git a/src/doc/src/reference/publishing.md b/src/doc/src/reference/publishing.md index 013faf6e246..98d572c346f 100644 --- a/src/doc/src/reference/publishing.md +++ b/src/doc/src/reference/publishing.md @@ -34,6 +34,10 @@ This command will inform Cargo of your API token and store it locally in your shared with anyone else. If it leaks for any reason, you should revoke it immediately. +> **Note**: The [`cargo logout`] command can be used to remove the token from +> `credentials.toml`. This can be useful if you no longer need it stored on +> the local machine. + ### Before publishing a new crate Keep in mind that crate names on [crates.io] are allocated on a first-come-first-serve @@ -264,6 +268,7 @@ request the org owner to do so. [Rust API Guidelines]: https://rust-lang.github.io/api-guidelines/ [`cargo login`]: ../commands/cargo-login.md +[`cargo logout`]: ../commands/cargo-logout.md [`cargo package`]: ../commands/cargo-package.md [`cargo publish`]: ../commands/cargo-publish.md [`categories`]: manifest.md#the-categories-field diff --git a/src/doc/src/reference/registry-index.md b/src/doc/src/reference/registry-index.md index ceba14808ea..38e3dd5fe6a 100644 --- a/src/doc/src/reference/registry-index.md +++ b/src/doc/src/reference/registry-index.md @@ -205,6 +205,40 @@ explaining the format of the entry. The JSON objects should not be modified after they are added except for the `yanked` field whose value may change at any time. +> **Note**: The index JSON format has subtle differences from the JSON format of the [Publish API] and [`cargo metadata`]. +> If you are using one of those as a source to generate index entries, you are encouraged to carefully inspect the documentation differences between them. +> +> For the [Publish API], the differences are: +> +> * `deps` +> * `name` --- When the dependency is [renamed] in `Cargo.toml`, the publish API puts the original package name in the `name` field and the aliased name in the `explicit_name_in_toml` field. +> The index places the aliased name in the `name` field, and the original package name in the `package` field. +> * `req` --- The Publish API field is called `version_req`. +> * `cksum` --- The publish API does not specify the checksum, it must be computed by the registry before adding to the index. +> * `features` --- Some features may be placed in the `features2` field. +> Note: This is only a legacy requirement for [crates.io]; other registries should not need to bother with modifying the features map. +> The `v` field indicates the presence of the `features2` field. +> * The publish API includes several other fields, such as `description` and `readme`, which don't appear in the index. +> These are intended to make it easier for a registry to obtain the metadata about the crate to display on a website without needing to extract and parse the `.crate` file. +> This additional information is typically added to a database on the registry server. +> +> For [`cargo metadata`], the differences are: +> +> * `vers` --- The `cargo metadata` field is called `version`. +> * `deps` +> * `name` --- When the dependency is [renamed] in `Cargo.toml`, `cargo metadata` puts the original package name in the `name` field and the aliased name in the `rename` field. +> The index places the aliased name in the `name` field, and the original package name in the `package` field. +> * `default_features` --- The `cargo metadata` field is called `uses_default_features`. +> * `registry` --- `cargo metadata` uses a value of `null` to indicate that the dependency comes from [crates.io]. +> The index uses a value of `null` to indicate that the dependency comes from the same registry as the index. +> When creating an index entry, a registry other than [crates.io] should translate a value of `null` to be `https://github.com/rust-lang/crates.io-index` and translate a URL that matches the current index to be `null`. +> * `cargo metadata` includes some extra fields, such as `source` and `path`. +> * The index includes additional fields such as `yanked`, `cksum`, and `v`. + +[renamed]: specifying-dependencies.md#renaming-dependencies-in-cargotoml +[Publish API]: registry-web-api.md#publish +[`cargo metadata`]: ../commands/cargo-metadata.md + ### Index Protocols Cargo supports two remote registry protocols: `git` and `sparse`. The `git` protocol stores index files in a git repository and the `sparse` protocol fetches individual @@ -222,7 +256,7 @@ The sparse protocol uses the `sparse+` protocol prefix in the registry URL. For the sparse index URL for [crates.io] is `sparse+https://index.crates.io/`. The sparse protocol downloads each index file using an individual HTTP request. Since -this results in a large number of small HTTP requests, performance is signficiantly +this results in a large number of small HTTP requests, performance is significantly improved with a server that supports pipelining and HTTP/2. ##### Caching @@ -245,7 +279,7 @@ or 451 "Unavailable For Legal Reasons" code. ##### Sparse Limitations Since the URL of the registry is stored in the lockfile, it's not recommended to offer a registry with both protocols. Discussion about a transition plan is ongoing in issue -[#10964]. The [crates.io] registry is an exception, since Cargo internally substitues +[#10964]. The [crates.io] registry is an exception, since Cargo internally substitutes the equivalent git URL when the sparse protocol is used. If a registry does offer both protocols, it's currently recommended to choose one protocol diff --git a/src/doc/src/reference/registry-web-api.md b/src/doc/src/reference/registry-web-api.md index 618ae0b84bd..147ababd54e 100644 --- a/src/doc/src/reference/registry-web-api.md +++ b/src/doc/src/reference/registry-web-api.md @@ -57,6 +57,10 @@ The publish endpoint is used to publish a new version of a crate. The server should validate the crate, make it available for download, and add it to the index. +It is not required for the index to be updated before the successful response is sent. +After a successful response, Cargo will poll the index for a short period of time to identify that the new crate has been added. +If the crate does not appear in the index after a short period of time, then Cargo will display a warning letting the user know that the new crate is not yet available. + The body of the data sent by Cargo is: - 32-bit unsigned little-endian integer of the length of JSON data. diff --git a/src/doc/src/reference/semver.md b/src/doc/src/reference/semver.md index b7274c259cb..22c42610f17 100644 --- a/src/doc/src/reference/semver.md +++ b/src/doc/src/reference/semver.md @@ -702,7 +702,7 @@ impl Trait for Foo {} fn main() { let x = Foo; - x.foo(1); // Error: this function takes 0 arguments + x.foo(1); // Error: this method takes 0 arguments but 1 argument was supplied } ``` @@ -944,7 +944,7 @@ pub fn foo() {} use updated_crate::foo; fn main() { - foo::(); // Error: this function takes 2 generic arguments but 1 generic argument was supplied + foo::(); // Error: function takes 2 generic arguments but 1 generic argument was supplied } ``` diff --git a/src/doc/src/reference/timings.md b/src/doc/src/reference/timings.md index 9e6863306f4..b978d52cded 100644 --- a/src/doc/src/reference/timings.md +++ b/src/doc/src/reference/timings.md @@ -12,13 +12,21 @@ filename, if you want to look at older runs. #### Reading the graphs -There are two graphs in the output. The "unit" graph shows the duration of -each unit over time. A "unit" is a single compiler invocation. There are lines -that show which additional units are "unlocked" when a unit finishes. That is, -it shows the new units that are now allowed to run because their dependencies -are all finished. Hover the mouse over a unit to highlight the lines. This can -help visualize the critical path of dependencies. This may change between runs -because the units may finish in different orders. +There are two tables and two graphs in the output. + +The first table displays the build information of the project, including the +number of units built, the maximum number of concurrency, build time, and the +version information of the currently used compiler. + +![build-info](../images/build-info.png) + +The "unit" graph shows the duration of each unit over time. A "unit" is a single +compiler invocation. There are lines that show which additional units are +"unlocked" when a unit finishes. That is, it shows the new units that are now +allowed to run because their dependencies are all finished. Hover the mouse over +a unit to highlight the lines. This can help visualize the critical path of +dependencies. This may change between runs because the units may finish in +different orders. The "codegen" times are highlighted in a lavender color. In some cases, build pipelining allows units to start when their dependencies are performing code @@ -28,6 +36,8 @@ units do not show when code generation starts). The "custom build" units are `build.rs` scripts, which when run are highlighted in orange. +![build-unit-time](../images/build-unit-time.png) + The second graph shows Cargo's concurrency over time. The background indicates CPU usage. The three lines are: - "Waiting" (red) --- This is the number of units waiting for a CPU slot to @@ -36,6 +46,8 @@ indicates CPU usage. The three lines are: dependencies to finish. - "Active" (green) --- This is the number of units currently running. +![cargo-concurrency-over-time](../images/cargo-concurrency-over-time.png) + Note: This does not show the concurrency in the compiler itself. `rustc` coordinates with Cargo via the "job server" to stay within the concurrency limit. This currently mostly applies to the code generation phase. @@ -49,3 +61,6 @@ Tips for addressing compile times: - Split large crates into smaller pieces. - If there are a large number of crates bottlenecked on a single crate, focus your attention on improving that one crate to improve parallelism. + +The last table lists the total time and "codegen" time spent on each unit, +as well as the features that were enabled during each unit's compilation. diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index 0b21118a88e..accd45d8ee8 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -67,6 +67,7 @@ Each new feature described below should explain how to use it. * [no-index-update](#no-index-update) --- Prevents cargo from updating the index cache. * [avoid-dev-deps](#avoid-dev-deps) --- Prevents the resolver from including dev-dependencies during resolution. * [minimal-versions](#minimal-versions) --- Forces the resolver to use the lowest compatible version instead of the highest. + * [direct-minimal-versions](#direct-minimal-versions) — Forces the resolver to use the lowest compatible version instead of the highest. * [public-dependency](#public-dependency) --- Allows dependencies to be classified as either public or private. * Output behavior * [out-dir](#out-dir) --- Adds a directory where artifacts are copied to. @@ -79,9 +80,13 @@ Each new feature described below should explain how to use it. * [binary-dep-depinfo](#binary-dep-depinfo) --- Causes the dep-info file to track binary dependencies. * [panic-abort-tests](#panic-abort-tests) --- Allows running tests with the "abort" panic strategy. * [keep-going](#keep-going) --- Build as much as possible rather than aborting on the first error. + * [check-cfg](#check-cfg) --- Compile-time validation of `cfg` expressions. + * [host-config](#host-config) --- Allows setting `[target]`-like configuration settings for host build targets. + * [target-applies-to-host](#target-applies-to-host) --- Alters whether certain flags will be passed to host build targets. * rustdoc * [`doctest-in-workspace`](#doctest-in-workspace) --- Fixes workspace-relative paths when running doctests. * [rustdoc-map](#rustdoc-map) --- Provides mappings for documentation to link to external sites like [docs.rs](https://docs.rs/). + * [scrape-examples](#scrape-examples) --- Shows examples within documentation. * `Cargo.toml` extensions * [Profile `rustflags` option](#profile-rustflags-option) --- Passed directly to rustc. * [codegen-backend](#codegen-backend) --- Select the codegen backend used by rustc. @@ -96,9 +101,10 @@ Each new feature described below should explain how to use it. * [`cargo config`](#cargo-config) --- Adds a new subcommand for viewing config files. * Registries * [credential-process](#credential-process) --- Adds support for fetching registry tokens from an external authentication program. - * [`cargo logout`](#cargo-logout) --- Adds the `logout` command to remove the currently saved registry token. * [publish-timeout](#publish-timeout) --- Controls the timeout between uploading the crate and being available in the index * [registry-auth](#registry-auth) --- Adds support for authenticated registries, and generate registry authentication tokens using asymmetric cryptography. +* Other + * [gitoxide](#gitoxide) --- Use `gitoxide` instead of `git2` for a set of operations. ### allow-features @@ -176,6 +182,23 @@ minimum versions that you are actually using. That is, if Cargo.toml says `foo = "1.0.0"` that you don't accidentally depend on features added only in `foo 1.5.0`. +### direct-minimal-versions +* Original Issue: [#4100](https://github.com/rust-lang/cargo/issues/4100) +* Tracking Issue: [#5657](https://github.com/rust-lang/cargo/issues/5657) + +When a `Cargo.lock` file is generated, the `-Z direct-minimal-versions` flag will +resolve the dependencies to the minimum SemVer version that will satisfy the +requirements (instead of the greatest version) for direct dependencies only. + +The intended use-case of this flag is to check, during continuous integration, +that the versions specified in Cargo.toml are a correct reflection of the +minimum versions that you are actually using. That is, if Cargo.toml says +`foo = "1.0.0"` that you don't accidentally depend on features added only in +`foo 1.5.0`. + +Indirect dependencies are resolved as normal so as not to be blocked on their +minimal version validation. + ### out-dir * Original Issue: [#4875](https://github.com/rust-lang/cargo/issues/4875) * Tracking Issue: [#6790](https://github.com/rust-lang/cargo/issues/6790) @@ -215,20 +238,6 @@ information from `.cargo/config.toml`. See the rustc issue for more information. cargo test --target foo -Zdoctest-xcompile ``` -#### New `dir-name` attribute - -Some of the paths generated under `target/` have resulted in a de-facto "build -protocol", where `cargo` is invoked as a part of a larger project build. So, to -preserve the existing behavior, there is also a new attribute `dir-name`, which -when left unspecified, defaults to the name of the profile. For example: - -```toml -[profile.release-lto] -inherits = "release" -dir-name = "lto" # Emits to target/lto instead of target/release-lto -lto = true -``` - ### Build-plan * Tracking Issue: [#5579](https://github.com/rust-lang/cargo/issues/5579) @@ -895,12 +904,11 @@ The `credential-process` feature adds a config setting to fetch registry authentication tokens by calling an external process. Token authentication is used by the [`cargo login`], [`cargo publish`], -[`cargo owner`], and [`cargo yank`] commands. Additionally, this feature adds -a new `cargo logout` command. +[`cargo owner`], [`cargo yank`], and [`cargo logout`] commands. To use this feature, you must pass the `-Z credential-process` flag on the command-line. Additionally, you must remove any current tokens currently saved -in the [`credentials.toml` file] (which can be done with the new `logout` command). +in the [`credentials.toml` file] (which can be done with the [`cargo logout`] command). #### `credential-process` Configuration @@ -989,7 +997,7 @@ A basic authenticator is a process that returns a token on stdout. Newlines will be trimmed. The process inherits the user's stdin and stderr. It should exit 0 on success, and nonzero on error. -With this form, [`cargo login`] and `cargo logout` are not supported and +With this form, [`cargo login`] and [`cargo logout`] are not supported and return an error if used. ##### Cargo authenticator @@ -1034,28 +1042,8 @@ The following environment variables will be provided to the executed command: * `CARGO_REGISTRY_INDEX_URL` --- The URL of the registry index. * `CARGO_REGISTRY_NAME_OPT` --- Optional name of the registry. Should not be used as a storage key. Not always available. -#### `cargo logout` - -A new `cargo logout` command has been added to make it easier to remove a -token from storage. This supports both [`credentials.toml` file] tokens and -`credential-process` tokens. - -When used with `credentials.toml` file tokens, it needs the `-Z unstable-options` -command-line option: - -```console -cargo logout -Z unstable-options -``` - -When used with the `credential-process` config, use the `-Z -credential-process` command-line option: - - -```console -cargo logout -Z credential-process -``` - [`cargo login`]: ../commands/cargo-login.md +[`cargo logout`]: ../commands/cargo-logout.md [`cargo publish`]: ../commands/cargo-publish.md [`cargo owner`]: ../commands/cargo-owner.md [`cargo yank`]: ../commands/cargo-yank.md @@ -1218,7 +1206,7 @@ println!("cargo:rustc-check-cfg=names(foo, bar)"); cargo check -Z unstable-options -Z check-cfg=output ``` -### `cargo:rustc-check-cfg=CHECK_CFG` +#### `cargo:rustc-check-cfg=CHECK_CFG` The `rustc-check-cfg` instruction tells Cargo to pass the given value to the `--check-cfg` flag to the compiler. This may be used for compile-time @@ -1259,6 +1247,23 @@ codegen-backend = true codegen-backend = "cranelift" ``` +### gitoxide + +* Tracking Issue: [#11813](https://github.com/rust-lang/cargo/issues/11813) + +With the 'gitoxide' unstable feature, all or the the specified git operations will be performed by +the `gitoxide` crate instead of `git2`. + +While `-Zgitoxide` enables all currently implemented features, one can individually select git operations +to run with `gitoxide` with the `-Zgitoxide=operation[,operationN]` syntax. + +Valid operations are the following: + +* `fetch` - All fetches are done with `gitoxide`, which includes git dependencies as well as the crates index. +* `shallow-index` *(planned)* - perform a shallow clone of the index. +* `shallow-deps` *(planned)* - perform a shallow clone of git dependencies. +* `checkout` *(planned)* - checkout the worktree, with support for filters and submodules. + ## Stabilized and removed features ### Compile progress @@ -1461,4 +1466,8 @@ terminal where Cargo can automatically detect the width. Sparse registry support has been stabilized in the 1.68 release. See [Registry Protocols](registries.md#registry-protocols) for more information. +#### `cargo logout` + +The [`cargo logout`] command has been stabilized in the 1.70 release. + [target triple]: ../appendix/glossary.md#target '"target" (glossary)' diff --git a/src/etc/_cargo b/src/etc/_cargo index df74c53a18f..bdceb10c97f 100644 --- a/src/etc/_cargo +++ b/src/etc/_cargo @@ -94,6 +94,7 @@ _cargo() { '(-p --package)'{-p+,--package=}'[specify package to run benchmarks for]:package:_cargo_package_names' \ '--exclude=[exclude packages from the benchmark]:spec' \ '--no-fail-fast[run all benchmarks regardless of failure]' \ + '--ignore-rust-version[Ignore rust-version specification in packages]' \ '1: :_guard "^-*" "bench name"' \ '*:args:_default' ;; @@ -105,6 +106,7 @@ _cargo() { '(-p --package)'{-p+,--package=}'[specify package to build]:package:_cargo_package_names' \ '--release[build in release mode]' \ '--build-plan[output the build plan in JSON]' \ + '--ignore-rust-version[Ignore rust-version specification in packages]' ;; check | c) @@ -113,6 +115,7 @@ _cargo() { "${command_scope_spec[@]}" \ '(-p --package)'{-p+,--package=}'[specify package to check]:package:_cargo_package_names' \ '--release[check in release mode]' \ + '--ignore-rust-version[Ignore rust-version specification in packages]' ;; clean) @@ -129,6 +132,7 @@ _cargo() { '--open[open docs in browser after the build]' \ '(-p --package)'{-p+,--package=}'[specify package to document]:package:_cargo_package_names' \ '--release[build artifacts in release mode, with optimizations]' \ + '--ignore-rust-version[Ignore rust-version specification in packages]' ;; fetch) @@ -143,7 +147,8 @@ _cargo() { '--edition-idioms[fix warnings to migrate to the idioms of an edition]' \ '--allow-no-vcs[fix code even if a VCS was not detected]' \ '--allow-dirty[fix code even if the working directory is dirty]' \ - '--allow-staged[fix code even if the working directory has staged changes]' + '--allow-staged[fix code even if the working directory has staged changes]' \ + '--ignore-rust-version[Ignore rust-version specification in packages]' ;; generate-lockfile) @@ -177,6 +182,7 @@ _cargo() { '--tag=[tag to use when installing from git]:tag' \ '--version=[version to install from crates.io]:version' \ '--list[list all installed packages and their versions]' \ + '--ignore-rust-version[Ignore rust-version specification in packages]' \ '*: :_guard "^-*" "crate"' ;; @@ -258,6 +264,7 @@ _cargo() { '--bin=[name of the bin target]:name' \ '(-p --package)'{-p+,--package=}'[specify package with the target to run]:package:_cargo_package_names' \ '--release[build in release mode]' \ + '--ignore-rust-version[Ignore rust-version specification in packages]' \ '*: :_default' ;; @@ -267,6 +274,7 @@ _cargo() { '--profile=[specify profile to build the selected target for]:profile' \ '--release[build artifacts in release mode, with optimizations]' \ "${command_scope_spec[@]}" \ + '--ignore-rust-version[Ignore rust-version specification in packages]' \ '*: : _dispatch rustc rustc -default-' ;; @@ -277,6 +285,7 @@ _cargo() { '(-p --package)'{-p+,--package=}'[specify package to document]:package:_cargo_package_names' \ '--release[build artifacts in release mode, with optimizations]' \ "${command_scope_spec[@]}" \ + '--ignore-rust-version[Ignore rust-version specification in packages]' \ '*: : _dispatch rustdoc rustdoc -default-' ;; @@ -302,6 +311,7 @@ _cargo() { '(--lib --doc --bin --test --bench)--example=[example name]:_cargo_example_names' \ '(--lib --doc --bin --example --bench)--test=[test name]' \ '(--lib --doc --bin --example --test)--bench=[benchmark name]' \ + '--ignore-rust-version[Ignore rust-version specification in packages]' \ '*: :_default' ;; diff --git a/src/etc/cargo.bashcomp.sh b/src/etc/cargo.bashcomp.sh index cb709dccb8b..2867ec56dd5 100644 --- a/src/etc/cargo.bashcomp.sh +++ b/src/etc/cargo.bashcomp.sh @@ -49,21 +49,21 @@ _cargo() local opt___nocmd="$opt_common -V --version --list --explain" local opt__add="$opt_common -p --package --features --default-features --no-default-features $opt_mani --optional --no-optional --rename --dry-run --path --git --branch --tag --rev --registry --dev --build --target" - local opt__bench="$opt_common $opt_pkg_spec $opt_feat $opt_mani $opt_lock $opt_parallel $opt_targets --message-format --target --no-run --no-fail-fast --target-dir" - local opt__build="$opt_common $opt_pkg_spec $opt_feat $opt_mani $opt_lock $opt_parallel $opt_targets --message-format --target --release --profile --target-dir" + local opt__bench="$opt_common $opt_pkg_spec $opt_feat $opt_mani $opt_lock $opt_parallel $opt_targets --message-format --target --no-run --no-fail-fast --target-dir --ignore-rust-version" + local opt__build="$opt_common $opt_pkg_spec $opt_feat $opt_mani $opt_lock $opt_parallel $opt_targets --message-format --target --release --profile --target-dir --ignore-rust-version" local opt__b="$opt__build" - local opt__check="$opt_common $opt_pkg_spec $opt_feat $opt_mani $opt_lock $opt_parallel $opt_targets --message-format --target --release --profile --target-dir" + local opt__check="$opt_common $opt_pkg_spec $opt_feat $opt_mani $opt_lock $opt_parallel $opt_targets --message-format --target --release --profile --target-dir --ignore-rust-version" local opt__c="$opt__check" local opt__clean="$opt_common $opt_pkg $opt_mani $opt_lock --target --release --doc --target-dir --profile" local opt__clippy="$opt_common $opt_pkg_spec $opt_feat $opt_mani $opt_lock $opt_parallel $opt_targets --message-format --target --release --profile --target-dir --no-deps --fix" - local opt__doc="$opt_common $opt_pkg_spec $opt_feat $opt_mani $opt_lock $opt_parallel --message-format --bin --bins --lib --target --open --no-deps --release --document-private-items --target-dir --profile" + local opt__doc="$opt_common $opt_pkg_spec $opt_feat $opt_mani $opt_lock $opt_parallel --message-format --bin --bins --lib --target --open --no-deps --release --document-private-items --target-dir --profile --ignore-rust-version" local opt__d="$opt__doc" local opt__fetch="$opt_common $opt_mani $opt_lock --target" - local opt__fix="$opt_common $opt_pkg_spec $opt_feat $opt_mani $opt_parallel $opt_targets $opt_lock --release --target --message-format --broken-code --edition --edition-idioms --allow-no-vcs --allow-dirty --allow-staged --profile --target-dir" + local opt__fix="$opt_common $opt_pkg_spec $opt_feat $opt_mani $opt_parallel $opt_targets $opt_lock --release --target --message-format --broken-code --edition --edition-idioms --allow-no-vcs --allow-dirty --allow-staged --profile --target-dir --ignore-rust-version" local opt__generate_lockfile="$opt_common $opt_mani $opt_lock" local opt__help="$opt_help" local opt__init="$opt_common $opt_lock --bin --lib --name --vcs --edition --registry" - local opt__install="$opt_common $opt_feat $opt_parallel $opt_lock $opt_force --bin --bins --branch --debug --example --examples --git --list --path --rev --root --tag --version --registry --target --profile --no-track" + local opt__install="$opt_common $opt_feat $opt_parallel $opt_lock $opt_force --bin --bins --branch --debug --example --examples --git --list --path --rev --root --tag --version --registry --target --profile --no-track --ignore-rust-version" local opt__locate_project="$opt_common $opt_mani $opt_lock --message-format --workspace" local opt__login="$opt_common $opt_lock --registry" local opt__metadata="$opt_common $opt_feat $opt_mani $opt_lock --format-version=1 --no-deps --filter-platform" @@ -77,12 +77,12 @@ _cargo() local opt__rm="$opt__remove" local opt__report="$opt_help $opt_verbose $opt_color future-incompat future-incompatibilities" local opt__report__future_incompat="$opt_help $opt_verbose $opt_color $opt_pkg --id" - local opt__run="$opt_common $opt_pkg $opt_feat $opt_mani $opt_lock $opt_parallel --message-format --target --bin --example --release --target-dir --profile" + local opt__run="$opt_common $opt_pkg $opt_feat $opt_mani $opt_lock $opt_parallel --message-format --target --bin --example --release --target-dir --profile --ignore-rust-version" local opt__r="$opt__run" - local opt__rustc="$opt_common $opt_pkg $opt_feat $opt_mani $opt_lock $opt_parallel $opt_targets -L --crate-type --extern --message-format --profile --target --release --target-dir" - local opt__rustdoc="$opt_common $opt_pkg $opt_feat $opt_mani $opt_lock $opt_parallel $opt_targets --message-format --target --release --open --target-dir --profile" + local opt__rustc="$opt_common $opt_pkg $opt_feat $opt_mani $opt_lock $opt_parallel $opt_targets -L --crate-type --extern --message-format --profile --target --release --target-dir --ignore-rust-version" + local opt__rustdoc="$opt_common $opt_pkg $opt_feat $opt_mani $opt_lock $opt_parallel $opt_targets --message-format --target --release --open --target-dir --profile --ignore-rust-version" local opt__search="$opt_common $opt_lock --limit --index --registry" - local opt__test="$opt_common $opt_pkg_spec $opt_feat $opt_mani $opt_lock $opt_parallel $opt_targets --message-format --doc --target --no-run --release --no-fail-fast --target-dir --profile" + local opt__test="$opt_common $opt_pkg_spec $opt_feat $opt_mani $opt_lock $opt_parallel $opt_targets --message-format --doc --target --no-run --release --no-fail-fast --target-dir --profile --ignore-rust-version" local opt__t="$opt__test" local opt__tree="$opt_common $opt_pkg_spec $opt_feat $opt_mani $opt_lock --target -i --invert --prefix --no-dedupe --duplicates -d --charset -f --format -e --edges" local opt__uninstall="$opt_common $opt_lock $opt_pkg --bin --root" diff --git a/src/etc/man/cargo-add.1 b/src/etc/man/cargo-add.1 index c93db74f26b..086a561fd65 100644 --- a/src/etc/man/cargo-add.1 +++ b/src/etc/man/cargo-add.1 @@ -96,8 +96,9 @@ Add as a \fIbuild dependency\fR \&. +.sp +To avoid unexpected shell expansions, you may use quotes around each target, e.g., \fB\-\-target 'cfg(unix)'\fR\&. .RE - .SS "Dependency options" .sp \fB\-\-dry\-run\fR @@ -242,7 +243,13 @@ See the \fIcommand\-line overrides section\fR and +requires the \fB\-Z unstable\-options\fR flag to enable (see +\fI#10098\fR ). .RE .sp \fB\-h\fR, @@ -308,5 +315,15 @@ cargo add serde serde_json \-F serde/derive .fi .RE .RE +.sp +.RS 4 +\h'-04' 5.\h'+01'Add \fBwindows\fR as a platform specific dependency on \fBcfg(windows)\fR +.sp +.RS 4 +.nf +cargo add windows \-\-target 'cfg(windows)' +.fi +.RE +.RE .SH "SEE ALSO" \fBcargo\fR(1), \fBcargo\-remove\fR(1) diff --git a/src/etc/man/cargo-bench.1 b/src/etc/man/cargo-bench.1 index 8cc7195e517..b95902c4ecf 100644 --- a/src/etc/man/cargo-bench.1 +++ b/src/etc/man/cargo-bench.1 @@ -57,6 +57,12 @@ optimizations and disables debugging information. If you need to debug a benchmark, you can use the \fB\-\-profile=dev\fR command\-line option to switch to the dev profile. You can then run the debug\-enabled benchmark within a debugger. +.SS "Working directory of benchmarks" +The working directory of every benchmark is set to the root directory of the +package the benchmark belongs to. +Setting the working directory of benchmarks to the package\[cq]s root directory +makes it possible for benchmarks to reliably access the package\[cq]s files using +relative paths, regardless from where \fBcargo bench\fR was executed from. .SH "OPTIONS" .SS "Benchmark Options" .sp @@ -455,7 +461,13 @@ See the \fIcommand\-line overrides section\fR and +requires the \fB\-Z unstable\-options\fR flag to enable (see +\fI#10098\fR ). .RE .sp \fB\-h\fR, diff --git a/src/etc/man/cargo-build.1 b/src/etc/man/cargo-build.1 index d42d6a1006c..80ae4ac9092 100644 --- a/src/etc/man/cargo-build.1 +++ b/src/etc/man/cargo-build.1 @@ -385,7 +385,13 @@ See the \fIcommand\-line overrides section\fR and +requires the \fB\-Z unstable\-options\fR flag to enable (see +\fI#10098\fR ). .RE .sp \fB\-h\fR, diff --git a/src/etc/man/cargo-check.1 b/src/etc/man/cargo-check.1 index b18e2202ad2..cf7a66d89d7 100644 --- a/src/etc/man/cargo-check.1 +++ b/src/etc/man/cargo-check.1 @@ -366,7 +366,13 @@ See the \fIcommand\-line overrides section\fR and +requires the \fB\-Z unstable\-options\fR flag to enable (see +\fI#10098\fR ). .RE .sp \fB\-h\fR, diff --git a/src/etc/man/cargo-clean.1 b/src/etc/man/cargo-clean.1 index 7799cca8c04..3cb321f05f0 100644 --- a/src/etc/man/cargo-clean.1 +++ b/src/etc/man/cargo-clean.1 @@ -159,7 +159,13 @@ See the \fIcommand\-line overrides section\fR and +requires the \fB\-Z unstable\-options\fR flag to enable (see +\fI#10098\fR ). .RE .sp \fB\-h\fR, diff --git a/src/etc/man/cargo-doc.1 b/src/etc/man/cargo-doc.1 index 76f1a0914a1..63ce2a05036 100644 --- a/src/etc/man/cargo-doc.1 +++ b/src/etc/man/cargo-doc.1 @@ -333,7 +333,13 @@ See the \fIcommand\-line overrides section\fR and +requires the \fB\-Z unstable\-options\fR flag to enable (see +\fI#10098\fR ). .RE .sp \fB\-h\fR, diff --git a/src/etc/man/cargo-fetch.1 b/src/etc/man/cargo-fetch.1 index 9b8bc8f088c..3779b9c2862 100644 --- a/src/etc/man/cargo-fetch.1 +++ b/src/etc/man/cargo-fetch.1 @@ -133,7 +133,13 @@ See the \fIcommand\-line overrides section\fR and +requires the \fB\-Z unstable\-options\fR flag to enable (see +\fI#10098\fR ). .RE .sp \fB\-h\fR, diff --git a/src/etc/man/cargo-fix.1 b/src/etc/man/cargo-fix.1 index 3d842628816..51b1e3fd64e 100644 --- a/src/etc/man/cargo-fix.1 +++ b/src/etc/man/cargo-fix.1 @@ -461,7 +461,13 @@ See the \fIcommand\-line overrides section\fR and +requires the \fB\-Z unstable\-options\fR flag to enable (see +\fI#10098\fR ). .RE .sp \fB\-h\fR, diff --git a/src/etc/man/cargo-generate-lockfile.1 b/src/etc/man/cargo-generate-lockfile.1 index da4a5fbe675..075f6324c75 100644 --- a/src/etc/man/cargo-generate-lockfile.1 +++ b/src/etc/man/cargo-generate-lockfile.1 @@ -112,7 +112,13 @@ See the \fIcommand\-line overrides section\fR and +requires the \fB\-Z unstable\-options\fR flag to enable (see +\fI#10098\fR ). .RE .sp \fB\-h\fR, diff --git a/src/etc/man/cargo-init.1 b/src/etc/man/cargo-init.1 index f842fcef6e0..56d1aca9f66 100644 --- a/src/etc/man/cargo-init.1 +++ b/src/etc/man/cargo-init.1 @@ -125,7 +125,13 @@ See the \fIcommand\-line overrides section\fR and +requires the \fB\-Z unstable\-options\fR flag to enable (see +\fI#10098\fR ). .RE .sp \fB\-h\fR, diff --git a/src/etc/man/cargo-install.1 b/src/etc/man/cargo-install.1 index 32b2bf4c95a..d0e5c518167 100644 --- a/src/etc/man/cargo-install.1 +++ b/src/etc/man/cargo-install.1 @@ -273,6 +273,12 @@ Install with the given profile. See the \fIthe reference\fR for more details on profiles. .RE .sp +\fB\-\-ignore\-rust\-version\fR +.RS 4 +Install the target even if the selected Rust compiler is older than the +required Rust version as configured in the project\[cq]s \fBrust\-version\fR field. +.RE +.sp \fB\-\-timings=\fR\fIfmts\fR .RS 4 Output information how long each compilation takes, and track concurrency @@ -443,7 +449,13 @@ See the \fIcommand\-line overrides section\fR and +requires the \fB\-Z unstable\-options\fR flag to enable (see +\fI#10098\fR ). .RE .sp \fB\-h\fR, diff --git a/src/etc/man/cargo-locate-project.1 b/src/etc/man/cargo-locate-project.1 index 15e1f380feb..2fbbe183b9b 100644 --- a/src/etc/man/cargo-locate-project.1 +++ b/src/etc/man/cargo-locate-project.1 @@ -104,7 +104,13 @@ See the \fIcommand\-line overrides section\fR and +requires the \fB\-Z unstable\-options\fR flag to enable (see +\fI#10098\fR ). .RE .sp \fB\-h\fR, diff --git a/src/etc/man/cargo-login.1 b/src/etc/man/cargo-login.1 index 8e076cae97c..1ae1cc626ac 100644 --- a/src/etc/man/cargo-login.1 +++ b/src/etc/man/cargo-login.1 @@ -89,7 +89,13 @@ See the \fIcommand\-line overrides section\fR and +requires the \fB\-Z unstable\-options\fR flag to enable (see +\fI#10098\fR ). .RE .sp \fB\-h\fR, @@ -126,4 +132,4 @@ cargo login .RE .RE .SH "SEE ALSO" -\fBcargo\fR(1), \fBcargo\-publish\fR(1) +\fBcargo\fR(1), \fBcargo\-logout\fR(1), \fBcargo\-publish\fR(1) diff --git a/src/etc/man/cargo-logout.1 b/src/etc/man/cargo-logout.1 new file mode 100644 index 00000000000..7333cc62c0f --- /dev/null +++ b/src/etc/man/cargo-logout.1 @@ -0,0 +1,147 @@ +'\" t +.TH "CARGO\-LOGOUT" "1" +.nh +.ad l +.ss \n[.ss] 0 +.SH "NAME" +cargo\-logout \[em] Remove an API token from the registry locally +.SH "SYNOPSIS" +\fBcargo logout\fR [\fIoptions\fR] +.SH "DESCRIPTION" +This command will remove the API token from the local credential storage. +Credentials are stored in \fB$CARGO_HOME/credentials.toml\fR where \fB$CARGO_HOME\fR +defaults to \fB\&.cargo\fR in your home directory. +.sp +If \fB\-\-registry\fR is not specified, then the credentials for the default +registry will be removed (configured by +\fI\f(BIregistry.default\fI\fR , which defaults +to ). +.sp +This will not revoke the token on the server. If you need to revoke the token, +visit the registry website and follow its instructions (see + to revoke the token for ). +.SH "OPTIONS" +.SS "Logout Options" +.sp +\fB\-\-registry\fR \fIregistry\fR +.RS 4 +Name of the registry to use. Registry names are defined in \fICargo config +files\fR \&. If not specified, the default registry is used, +which is defined by the \fBregistry.default\fR config key which defaults to +\fBcrates\-io\fR\&. +.RE +.SS "Display Options" +.sp +\fB\-v\fR, +\fB\-\-verbose\fR +.RS 4 +Use verbose output. May be specified twice for \[lq]very verbose\[rq] output which +includes extra output such as dependency warnings and build script output. +May also be specified with the \fBterm.verbose\fR +\fIconfig value\fR \&. +.RE +.sp +\fB\-q\fR, +\fB\-\-quiet\fR +.RS 4 +Do not print cargo log messages. +May also be specified with the \fBterm.quiet\fR +\fIconfig value\fR \&. +.RE +.sp +\fB\-\-color\fR \fIwhen\fR +.RS 4 +Control when colored output is used. Valid values: +.sp +.RS 4 +\h'-04'\(bu\h'+02'\fBauto\fR (default): Automatically detect if color support is available on the +terminal. +.RE +.sp +.RS 4 +\h'-04'\(bu\h'+02'\fBalways\fR: Always display colors. +.RE +.sp +.RS 4 +\h'-04'\(bu\h'+02'\fBnever\fR: Never display colors. +.RE +.sp +May also be specified with the \fBterm.color\fR +\fIconfig value\fR \&. +.RE +.SS "Common Options" +.sp +\fB+\fR\fItoolchain\fR +.RS 4 +If Cargo has been installed with rustup, and the first argument to \fBcargo\fR +begins with \fB+\fR, it will be interpreted as a rustup toolchain name (such +as \fB+stable\fR or \fB+nightly\fR). +See the \fIrustup documentation\fR +for more information about how toolchain overrides work. +.RE +.sp +\fB\-\-config\fR \fIKEY=VALUE\fR or \fIPATH\fR +.RS 4 +Overrides a Cargo configuration value. The argument should be in TOML syntax of \fBKEY=VALUE\fR, +or provided as a path to an extra configuration file. This flag may be specified multiple times. +See the \fIcommand\-line overrides section\fR for more information. +.RE +.sp +\fB\-C\fR \fIPATH\fR +.RS 4 +Changes the current working directory before executing any specified operations. This affects +things like where cargo looks by default for the project manifest (\fBCargo.toml\fR), as well as +the directories searched for discovering \fB\&.cargo/config.toml\fR, for example. This option must +appear before the command name, for example \fBcargo \-C path/to/my\-project build\fR\&. +.sp +This option is only available on the \fInightly +channel\fR and +requires the \fB\-Z unstable\-options\fR flag to enable (see +\fI#10098\fR ). +.RE +.sp +\fB\-h\fR, +\fB\-\-help\fR +.RS 4 +Prints help information. +.RE +.sp +\fB\-Z\fR \fIflag\fR +.RS 4 +Unstable (nightly\-only) flags to Cargo. Run \fBcargo \-Z help\fR for details. +.RE +.SH "ENVIRONMENT" +See \fIthe reference\fR for +details on environment variables that Cargo reads. +.SH "EXIT STATUS" +.sp +.RS 4 +\h'-04'\(bu\h'+02'\fB0\fR: Cargo succeeded. +.RE +.sp +.RS 4 +\h'-04'\(bu\h'+02'\fB101\fR: Cargo failed to complete. +.RE +.SH "EXAMPLES" +.sp +.RS 4 +\h'-04' 1.\h'+01'Remove the default registry token: +.sp +.RS 4 +.nf +cargo logout +.fi +.RE +.RE +.sp +.RS 4 +\h'-04' 2.\h'+01'Remove the token for a specific registry: +.sp +.RS 4 +.nf +cargo logout \-\-registry my\-registry +.fi +.RE +.RE +.SH "SEE ALSO" +\fBcargo\fR(1), \fBcargo\-login\fR(1) diff --git a/src/etc/man/cargo-metadata.1 b/src/etc/man/cargo-metadata.1 index 9a7dd0fa95f..8549290cde5 100644 --- a/src/etc/man/cargo-metadata.1 +++ b/src/etc/man/cargo-metadata.1 @@ -437,7 +437,13 @@ See the \fIcommand\-line overrides section\fR and +requires the \fB\-Z unstable\-options\fR flag to enable (see +\fI#10098\fR ). .RE .sp \fB\-h\fR, diff --git a/src/etc/man/cargo-new.1 b/src/etc/man/cargo-new.1 index 5f01bba7f59..62e0eb157ec 100644 --- a/src/etc/man/cargo-new.1 +++ b/src/etc/man/cargo-new.1 @@ -120,7 +120,13 @@ See the \fIcommand\-line overrides section\fR and +requires the \fB\-Z unstable\-options\fR flag to enable (see +\fI#10098\fR ). .RE .sp \fB\-h\fR, diff --git a/src/etc/man/cargo-owner.1 b/src/etc/man/cargo-owner.1 index 1977aae5cca..82cac16aae3 100644 --- a/src/etc/man/cargo-owner.1 +++ b/src/etc/man/cargo-owner.1 @@ -131,7 +131,13 @@ See the \fIcommand\-line overrides section\fR and +requires the \fB\-Z unstable\-options\fR flag to enable (see +\fI#10098\fR ). .RE .sp \fB\-h\fR, diff --git a/src/etc/man/cargo-package.1 b/src/etc/man/cargo-package.1 index c2aa20067b0..9f4847d7d12 100644 --- a/src/etc/man/cargo-package.1 +++ b/src/etc/man/cargo-package.1 @@ -305,7 +305,13 @@ See the \fIcommand\-line overrides section\fR and +requires the \fB\-Z unstable\-options\fR flag to enable (see +\fI#10098\fR ). .RE .sp \fB\-h\fR, diff --git a/src/etc/man/cargo-pkgid.1 b/src/etc/man/cargo-pkgid.1 index 03cff2f4769..9ec1b8cb768 100644 --- a/src/etc/man/cargo-pkgid.1 +++ b/src/etc/man/cargo-pkgid.1 @@ -56,7 +56,7 @@ T}:T{ \fBhttps://github.com/rust\-lang/crates.io\-index#bitflags\fR T} T{ -\fIurl\fR\fB#\fR\fIname\fR\fB:\fR\fIversion\fR +\fIurl\fR\fB#\fR\fIname\fR\fB@\fR\fIversion\fR T}:T{ \fBhttps://github.com/rust\-lang/cargo#crates\-io@0.21.0\fR T} @@ -167,7 +167,13 @@ See the \fIcommand\-line overrides section\fR and +requires the \fB\-Z unstable\-options\fR flag to enable (see +\fI#10098\fR ). .RE .sp \fB\-h\fR, diff --git a/src/etc/man/cargo-publish.1 b/src/etc/man/cargo-publish.1 index 8f92e47f5b6..a54a7bcda60 100644 --- a/src/etc/man/cargo-publish.1 +++ b/src/etc/man/cargo-publish.1 @@ -255,7 +255,13 @@ See the \fIcommand\-line overrides section\fR and +requires the \fB\-Z unstable\-options\fR flag to enable (see +\fI#10098\fR ). .RE .sp \fB\-h\fR, diff --git a/src/etc/man/cargo-remove.1 b/src/etc/man/cargo-remove.1 index bc07ad23b6f..40498161711 100644 --- a/src/etc/man/cargo-remove.1 +++ b/src/etc/man/cargo-remove.1 @@ -25,6 +25,8 @@ Remove as a \fIbuild dependency\fR \&. +.sp +To avoid unexpected shell expansions, you may use quotes around each target, e.g., \fB\-\-target 'cfg(unix)'\fR\&. .RE .SS "Miscellaneous Options" .sp @@ -136,7 +138,13 @@ See the \fIcommand\-line overrides section\fR and +requires the \fB\-Z unstable\-options\fR flag to enable (see +\fI#10098\fR ). .RE .sp \fB\-h\fR, diff --git a/src/etc/man/cargo-run.1 b/src/etc/man/cargo-run.1 index b030ab99a13..7a85298ccd4 100644 --- a/src/etc/man/cargo-run.1 +++ b/src/etc/man/cargo-run.1 @@ -13,6 +13,10 @@ Run a binary or example of the local package. All the arguments following the two dashes (\fB\-\-\fR) are passed to the binary to run. If you\[cq]re passing arguments to both Cargo and the binary, the ones after \fB\-\-\fR go to the binary, the ones before go to Cargo. +.sp +Unlike \fBcargo\-test\fR(1) and \fBcargo\-bench\fR(1), \fBcargo run\fR sets the +working directory of the binary executed to the current working directory, same +as if it was executed in the shell directly. .SH "OPTIONS" .SS "Package Selection" By default, the package in the current working directory is selected. The \fB\-p\fR @@ -266,7 +270,13 @@ See the \fIcommand\-line overrides section\fR and +requires the \fB\-Z unstable\-options\fR flag to enable (see +\fI#10098\fR ). .RE .sp \fB\-h\fR, diff --git a/src/etc/man/cargo-rustc.1 b/src/etc/man/cargo-rustc.1 index 63796c5065a..6e901d9ecad 100644 --- a/src/etc/man/cargo-rustc.1 +++ b/src/etc/man/cargo-rustc.1 @@ -229,7 +229,7 @@ information about timing information. .RS 4 Build for the given crate type. This flag accepts a comma\-separated list of 1 or more crate types, of which the allowed values are the same as \fBcrate\-type\fR -field in the manifest for configurating a Cargo target. See +field in the manifest for configuring a Cargo target. See \fI\f(BIcrate\-type\fI field\fR for possible values. .sp @@ -384,7 +384,13 @@ See the \fIcommand\-line overrides section\fR and +requires the \fB\-Z unstable\-options\fR flag to enable (see +\fI#10098\fR ). .RE .sp \fB\-h\fR, diff --git a/src/etc/man/cargo-rustdoc.1 b/src/etc/man/cargo-rustdoc.1 index 11d3a0abaee..0c9a0e74a7d 100644 --- a/src/etc/man/cargo-rustdoc.1 +++ b/src/etc/man/cargo-rustdoc.1 @@ -352,7 +352,13 @@ See the \fIcommand\-line overrides section\fR and +requires the \fB\-Z unstable\-options\fR flag to enable (see +\fI#10098\fR ). .RE .sp \fB\-h\fR, diff --git a/src/etc/man/cargo-search.1 b/src/etc/man/cargo-search.1 index f6c748dde9e..245d4e65dd9 100644 --- a/src/etc/man/cargo-search.1 +++ b/src/etc/man/cargo-search.1 @@ -92,7 +92,13 @@ See the \fIcommand\-line overrides section\fR and +requires the \fB\-Z unstable\-options\fR flag to enable (see +\fI#10098\fR ). .RE .sp \fB\-h\fR, diff --git a/src/etc/man/cargo-test.1 b/src/etc/man/cargo-test.1 index 2cc443370fb..85260b067b0 100644 --- a/src/etc/man/cargo-test.1 +++ b/src/etc/man/cargo-test.1 @@ -53,6 +53,12 @@ and may change in the future; beware of depending on it. .sp See the \fIrustdoc book\fR for more information on writing doc tests. +.SS "Working directory of tests" +The working directory of every test is set to the root directory of the package +the test belongs to. +Setting the working directory of tests to the package\[cq]s root directory makes it +possible for tests to reliably access the package\[cq]s files using relative paths, +regardless from where \fBcargo test\fR was executed from. .SH "OPTIONS" .SS "Test Options" .sp @@ -474,7 +480,13 @@ See the \fIcommand\-line overrides section\fR and +requires the \fB\-Z unstable\-options\fR flag to enable (see +\fI#10098\fR ). .RE .sp \fB\-h\fR, diff --git a/src/etc/man/cargo-tree.1 b/src/etc/man/cargo-tree.1 index 5394c65e968..2abad97320b 100644 --- a/src/etc/man/cargo-tree.1 +++ b/src/etc/man/cargo-tree.1 @@ -380,7 +380,13 @@ See the \fIcommand\-line overrides section\fR and +requires the \fB\-Z unstable\-options\fR flag to enable (see +\fI#10098\fR ). .RE .sp \fB\-h\fR, diff --git a/src/etc/man/cargo-uninstall.1 b/src/etc/man/cargo-uninstall.1 index d3ead109267..304d5788a14 100644 --- a/src/etc/man/cargo-uninstall.1 +++ b/src/etc/man/cargo-uninstall.1 @@ -115,7 +115,13 @@ See the \fIcommand\-line overrides section\fR and +requires the \fB\-Z unstable\-options\fR flag to enable (see +\fI#10098\fR ). .RE .sp \fB\-h\fR, diff --git a/src/etc/man/cargo-update.1 b/src/etc/man/cargo-update.1 index 7f2f511b272..6f697b3ab19 100644 --- a/src/etc/man/cargo-update.1 +++ b/src/etc/man/cargo-update.1 @@ -152,7 +152,13 @@ See the \fIcommand\-line overrides section\fR and +requires the \fB\-Z unstable\-options\fR flag to enable (see +\fI#10098\fR ). .RE .sp \fB\-h\fR, diff --git a/src/etc/man/cargo-vendor.1 b/src/etc/man/cargo-vendor.1 index 9caf6e6a08d..cb46f67cde4 100644 --- a/src/etc/man/cargo-vendor.1 +++ b/src/etc/man/cargo-vendor.1 @@ -143,7 +143,13 @@ See the \fIcommand\-line overrides section\fR and +requires the \fB\-Z unstable\-options\fR flag to enable (see +\fI#10098\fR ). .RE .sp \fB\-h\fR, diff --git a/src/etc/man/cargo-verify-project.1 b/src/etc/man/cargo-verify-project.1 index 0b419cbcf31..d067dd66555 100644 --- a/src/etc/man/cargo-verify-project.1 +++ b/src/etc/man/cargo-verify-project.1 @@ -122,7 +122,13 @@ See the \fIcommand\-line overrides section\fR and +requires the \fB\-Z unstable\-options\fR flag to enable (see +\fI#10098\fR ). .RE .sp \fB\-h\fR, diff --git a/src/etc/man/cargo-yank.1 b/src/etc/man/cargo-yank.1 index 71de0ae37db..423c7bc9915 100644 --- a/src/etc/man/cargo-yank.1 +++ b/src/etc/man/cargo-yank.1 @@ -123,7 +123,13 @@ See the \fIcommand\-line overrides section\fR and +requires the \fB\-Z unstable\-options\fR flag to enable (see +\fI#10098\fR ). .RE .sp \fB\-h\fR, diff --git a/src/etc/man/cargo.1 b/src/etc/man/cargo.1 index ee9715d2b63..8f61e0699f2 100644 --- a/src/etc/man/cargo.1 +++ b/src/etc/man/cargo.1 @@ -120,6 +120,10 @@ available at \&. .br \ \ \ \ Save an API token from the registry locally. .sp +\fBcargo\-logout\fR(1) +.br +\ \ \ \ Remove an API token from the registry locally. +.sp \fBcargo\-owner\fR(1) .br \ \ \ \ Manage the owners of a crate on the registry. @@ -255,7 +259,13 @@ See the \fIcommand\-line overrides section\fR and +requires the \fB\-Z unstable\-options\fR flag to enable (see +\fI#10098\fR ). .RE .sp \fB\-h\fR, diff --git a/tests/testsuite/alt_registry.rs b/tests/testsuite/alt_registry.rs index d9bbf47d29d..97da909b83c 100644 --- a/tests/testsuite/alt_registry.rs +++ b/tests/testsuite/alt_registry.rs @@ -328,7 +328,10 @@ fn publish_with_registry_dependency() { [FINISHED] [..] [PACKAGED] [..] [UPLOADING] foo v0.0.1 [..] -[UPDATING] `alternative` index +[UPLOADED] foo v0.0.1 to registry `alternative` +note: Waiting for `foo v0.0.1` to be available at registry `alternative`. +You may press ctrl-c to skip waiting; the crate should be available shortly. +[PUBLISHED] foo v0.0.1 at registry `alternative` ", ) .run(); @@ -434,6 +437,33 @@ or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN", .run(); } +#[cargo_test] +fn cargo_registries_crates_io_protocol() { + let _ = RegistryBuilder::new() + .no_configure_token() + .alternative() + .build(); + // Should not produce a warning due to the registries.crates-io.protocol = 'sparse' configuration + let p = project() + .file("src/lib.rs", "") + .file( + ".cargo/config.toml", + "[registries.crates-io] + protocol = 'sparse'", + ) + .build(); + + p.cargo("publish --registry alternative") + .with_status(101) + .with_stderr( + "\ +[UPDATING] `alternative` index +error: no token found for `alternative`, please run `cargo login --registry alternative` +or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN", + ) + .run(); +} + #[cargo_test] fn publish_to_alt_registry() { let _reg = RegistryBuilder::new() @@ -457,10 +487,39 @@ fn publish_to_alt_registry() { [FINISHED] [..] [PACKAGED] [..] [UPLOADING] foo v0.0.1 [..] -[UPDATING] `alternative` index +[UPLOADED] foo v0.0.1 to registry `alternative` +note: Waiting for `foo v0.0.1` to be available at registry `alternative`. +You may press ctrl-c to skip waiting; the crate should be available shortly. +[PUBLISHED] foo v0.0.1 at registry `alternative` ", ) .run(); + + validate_alt_upload( + r#"{ + "authors": [], + "badges": {}, + "categories": [], + "deps": [], + "description": null, + "documentation": null, + "features": {}, + "homepage": null, + "keywords": [], + "license": null, + "license_file": null, + "links": null, + "name": "foo", + "readme": null, + "readme_file": null, + "repository": null, + "homepage": null, + "documentation": null, + "vers": "0.0.1" + }"#, + "foo-0.0.1.crate", + &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"], + ); } #[cargo_test] @@ -509,7 +568,10 @@ fn publish_with_crates_io_dep() { [FINISHED] [..] [PACKAGED] [..] [UPLOADING] foo v0.0.1 [..] -[UPDATING] `alternative` index +[UPLOADED] foo v0.0.1 to registry `alternative` +note: Waiting for `foo v0.0.1` to be available at registry `alternative`. +You may press ctrl-c to skip waiting; the crate should be available shortly. +[PUBLISHED] foo v0.0.1 at registry `alternative` ", ) .run(); diff --git a/tests/testsuite/artifact_dep.rs b/tests/testsuite/artifact_dep.rs index 358ca898fc0..ec6bb7103a2 100644 --- a/tests/testsuite/artifact_dep.rs +++ b/tests/testsuite/artifact_dep.rs @@ -1911,7 +1911,10 @@ fn publish_artifact_dep() { [PACKAGING] foo v0.1.0 [..] [PACKAGED] [..] [UPLOADING] foo v0.1.0 [..] -[UPDATING] [..] +[UPLOADED] foo v0.1.0 [..] +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] foo v0.1.0 [..] ", ) .run(); diff --git a/tests/testsuite/bad_config.rs b/tests/testsuite/bad_config.rs index dabdc149cfe..ca51b101e39 100644 --- a/tests/testsuite/bad_config.rs +++ b/tests/testsuite/bad_config.rs @@ -1,5 +1,6 @@ //! Tests for some invalid .cargo/config files. +use cargo_test_support::git::cargo_uses_gitoxide; use cargo_test_support::registry::{self, Package}; use cargo_test_support::{basic_manifest, project, rustc_host}; @@ -334,23 +335,45 @@ fn bad_git_dependency() { let p = project() .file( "Cargo.toml", - r#" + &format!( + r#" [package] name = "foo" version = "0.0.0" authors = [] [dependencies] - foo = { git = "file:.." } + foo = {{ git = "{url}" }} "#, + url = if cargo_uses_gitoxide() { + "git://host.xz" + } else { + "file:.." + } + ), ) .file("src/lib.rs", "") .build(); - p.cargo("check -v") - .with_status(101) - .with_stderr( - "\ + let expected_stderr = if cargo_uses_gitoxide() { + "\ +[UPDATING] git repository `git://host.xz` +[ERROR] failed to get `foo` as a dependency of package `foo v0.0.0 [..]` + +Caused by: + failed to load source for dependency `foo` + +Caused by: + Unable to update git://host.xz + +Caused by: + failed to clone into: [..] + +Caused by: + URLs need to specify the path to the repository +" + } else { + "\ [UPDATING] git repository `file:///` [ERROR] failed to get `foo` as a dependency of package `foo v0.0.0 [..]` @@ -365,8 +388,11 @@ Caused by: Caused by: [..]'file:///' is not a valid local file URI[..] -", - ) +" + }; + p.cargo("check -v") + .with_status(101) + .with_stderr(expected_stderr) .run(); } diff --git a/tests/testsuite/build.rs b/tests/testsuite/build.rs index ca7f669b2e1..8a1b6ca86c1 100644 --- a/tests/testsuite/build.rs +++ b/tests/testsuite/build.rs @@ -159,6 +159,30 @@ fn cargo_compile_manifest_path() { assert!(p.bin("foo").is_file()); } +#[cargo_test] +fn chdir_gated() { + let p = project() + .file("Cargo.toml", &basic_bin_manifest("foo")) + .build(); + p.cargo("-C foo build") + .cwd(p.root().parent().unwrap()) + .with_stderr( + "error: the `-C` flag is unstable, \ + pass `-Z unstable-options` on the nightly channel to enable it", + ) + .with_status(101) + .run(); + // No masquerade should also fail. + p.cargo("-C foo -Z unstable-options build") + .cwd(p.root().parent().unwrap()) + .with_stderr( + "error: the `-C` flag is unstable, \ + pass `-Z unstable-options` on the nightly channel to enable it", + ) + .with_status(101) + .run(); +} + #[cargo_test] fn cargo_compile_directory_not_cwd() { let p = project() @@ -167,7 +191,8 @@ fn cargo_compile_directory_not_cwd() { .file(".cargo/config.toml", &"") .build(); - p.cargo("-C foo build") + p.cargo("-Zunstable-options -C foo build") + .masquerade_as_nightly_cargo(&["chdir"]) .cwd(p.root().parent().unwrap()) .run(); assert!(p.bin("foo").is_file()); @@ -181,7 +206,8 @@ fn cargo_compile_directory_not_cwd_with_invalid_config() { .file(".cargo/config.toml", &"!") .build(); - p.cargo("-C foo build") + p.cargo("-Zunstable-options -C foo build") + .masquerade_as_nightly_cargo(&["chdir"]) .cwd(p.root().parent().unwrap()) .with_status(101) .with_stderr_contains( @@ -639,7 +665,9 @@ fn cargo_compile_with_invalid_code() { p.cargo("build") .with_status(101) - .with_stderr_contains("[ERROR] could not compile `foo` due to previous error\n") + .with_stderr_contains( + "[ERROR] could not compile `foo` (bin \"foo\") due to previous error\n", + ) .run(); assert!(p.root().join("Cargo.lock").is_file()); } @@ -1370,6 +1398,7 @@ fn crate_env_vars() { license = "MIT OR Apache-2.0" license-file = "license.txt" rust-version = "1.61.0" + readme = "../../README.md" [[bin]] name = "foo-bar" @@ -1395,6 +1424,7 @@ fn crate_env_vars() { static LICENSE_FILE: &'static str = env!("CARGO_PKG_LICENSE_FILE"); static DESCRIPTION: &'static str = env!("CARGO_PKG_DESCRIPTION"); static RUST_VERSION: &'static str = env!("CARGO_PKG_RUST_VERSION"); + static README: &'static str = env!("CARGO_PKG_README"); static BIN_NAME: &'static str = env!("CARGO_BIN_NAME"); static CRATE_NAME: &'static str = env!("CARGO_CRATE_NAME"); @@ -1414,6 +1444,7 @@ fn crate_env_vars() { assert_eq!("license.txt", LICENSE_FILE); assert_eq!("This is foo", DESCRIPTION); assert_eq!("1.61.0", RUST_VERSION); + assert_eq!("../../README.md", README); let s = format!("{}.{}.{}-{}", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, VERSION_PRE); assert_eq!(s, VERSION); @@ -5699,7 +5730,7 @@ fn signal_display() { "\ [COMPILING] pm [..] [COMPILING] foo [..] -[ERROR] could not compile `foo` +[ERROR] could not compile `foo` [..] Caused by: process didn't exit successfully: `rustc [..]` (signal: 6, SIGABRT: process abort signal) diff --git a/tests/testsuite/build_script.rs b/tests/testsuite/build_script.rs index ccccfca71a0..80a24960edd 100644 --- a/tests/testsuite/build_script.rs +++ b/tests/testsuite/build_script.rs @@ -1678,7 +1678,7 @@ fn build_deps_not_for_normal() { .with_stderr_contains("[..]can't find crate for `aaaaa`[..]") .with_stderr_contains( "\ -[ERROR] could not compile `foo` due to previous error +[ERROR] could not compile `foo` (lib) due to previous error Caused by: process didn't exit successfully: [..] diff --git a/tests/testsuite/build_script_env.rs b/tests/testsuite/build_script_env.rs index 6ad4ab97b19..bc87b7120a5 100644 --- a/tests/testsuite/build_script_env.rs +++ b/tests/testsuite/build_script_env.rs @@ -239,3 +239,65 @@ fn cfg_paradox() { .with_stderr_contains("[..]--cfg=bertrand[..]") .run(); } + +/// This test checks how Cargo handles rustc cfgs which are defined both with +/// and without a value. The expected behavior is that the environment variable +/// is going to contain all the values. +/// +/// For example, this configuration: +/// ``` +/// target_has_atomic +/// target_has_atomic="16" +/// target_has_atomic="32" +/// target_has_atomic="64" +/// target_has_atomic="8" +/// target_has_atomic="ptr" +/// ``` +/// +/// Should result in the following environment variable: +/// +/// ``` +/// CARGO_CFG_TARGET_HAS_ATOMIC=16,32,64,8,ptr +/// ``` +/// +/// On the other hand, configuration symbols without any value should result in +/// an empty string. +/// +/// For example, this configuration: +/// +/// ``` +/// target_thread_local +/// ``` +/// +/// Should result in the following environment variable: +/// +/// ``` +/// CARGO_CFG_TARGET_THREAD_LOCAL= +/// ``` +#[cargo_test(nightly, reason = "affected rustc cfg is unstable")] +#[cfg(target_arch = "x86_64")] +fn rustc_cfg_with_and_without_value() { + let build_rs = r#" + fn main() { + let cfg = std::env::var("CARGO_CFG_TARGET_HAS_ATOMIC"); + eprintln!("CARGO_CFG_TARGET_HAS_ATOMIC={cfg:?}"); + let cfg = std::env::var("CARGO_CFG_WINDOWS"); + eprintln!("CARGO_CFG_WINDOWS={cfg:?}"); + let cfg = std::env::var("CARGO_CFG_UNIX"); + eprintln!("CARGO_CFG_UNIX={cfg:?}"); + } + "#; + let p = project() + .file("src/lib.rs", r#""#) + .file("build.rs", build_rs) + .build(); + + let mut check = p.cargo("check -vv"); + #[cfg(target_has_atomic = "64")] + check.with_stderr_contains("[foo 0.0.1] CARGO_CFG_TARGET_HAS_ATOMIC=Ok(\"[..]64[..]\")"); + #[cfg(windows)] + check.with_stderr_contains("[foo 0.0.1] CARGO_CFG_WINDOWS=Ok(\"\")"); + #[cfg(unix)] + check.with_stderr_contains("[foo 0.0.1] CARGO_CFG_UNIX=Ok(\"\")"); + check.run(); +} diff --git a/tests/testsuite/cargo_add/cyclic_features/in b/tests/testsuite/cargo_add/cyclic_features/in new file mode 120000 index 00000000000..6c6a27fcfb5 --- /dev/null +++ b/tests/testsuite/cargo_add/cyclic_features/in @@ -0,0 +1 @@ +../add-basic.in \ No newline at end of file diff --git a/tests/testsuite/cargo_add/cyclic_features/mod.rs b/tests/testsuite/cargo_add/cyclic_features/mod.rs new file mode 100644 index 00000000000..5dffac323c6 --- /dev/null +++ b/tests/testsuite/cargo_add/cyclic_features/mod.rs @@ -0,0 +1,25 @@ +use cargo_test_support::compare::assert_ui; +use cargo_test_support::prelude::*; +use cargo_test_support::Project; + +use crate::cargo_add::init_registry; +use cargo_test_support::curr_dir; + +#[cargo_test] +fn case() { + init_registry(); + let project = Project::from_template(curr_dir!().join("in")); + let project_root = project.root(); + let cwd = &project_root; + + snapbox::cmd::Command::cargo_ui() + .arg("add") + .arg_line("test_cyclic_features") + .current_dir(cwd) + .assert() + .success() + .stdout_matches_path(curr_dir!().join("stdout.log")) + .stderr_matches_path(curr_dir!().join("stderr.log")); + + assert_ui().subset_matches(curr_dir!().join("out"), &project_root); +} diff --git a/tests/testsuite/cargo_add/cyclic_features/out/Cargo.toml b/tests/testsuite/cargo_add/cyclic_features/out/Cargo.toml new file mode 100644 index 00000000000..27a5c31f867 --- /dev/null +++ b/tests/testsuite/cargo_add/cyclic_features/out/Cargo.toml @@ -0,0 +1,8 @@ +[workspace] + +[package] +name = "cargo-list-test-fixture" +version = "0.0.0" + +[dependencies] +test_cyclic_features = "0.1.1" diff --git a/tests/testsuite/cargo_add/cyclic_features/stderr.log b/tests/testsuite/cargo_add/cyclic_features/stderr.log new file mode 100644 index 00000000000..2d4a2db4a37 --- /dev/null +++ b/tests/testsuite/cargo_add/cyclic_features/stderr.log @@ -0,0 +1,5 @@ + Updating `dummy-registry` index + Adding test_cyclic_features v0.1.1 to dependencies. + Features: + + feature-one + + feature-two diff --git a/tests/testsuite/cargo_add/cyclic_features/stdout.log b/tests/testsuite/cargo_add/cyclic_features/stdout.log new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/testsuite/cargo_add/invalid_arg/stderr.log b/tests/testsuite/cargo_add/invalid_arg/stderr.log index b5ee3cca39c..96d067ed1fd 100644 --- a/tests/testsuite/cargo_add/invalid_arg/stderr.log +++ b/tests/testsuite/cargo_add/invalid_arg/stderr.log @@ -1,6 +1,6 @@ error: unexpected argument '--flag' found - note: argument '--tag' exists + tip: a similar argument exists: '--tag' Usage: cargo add [OPTIONS] [@] ... cargo add [OPTIONS] --path ... diff --git a/tests/testsuite/cargo_add/mod.rs b/tests/testsuite/cargo_add/mod.rs index d824aed0a42..ca58474d219 100644 --- a/tests/testsuite/cargo_add/mod.rs +++ b/tests/testsuite/cargo_add/mod.rs @@ -4,6 +4,7 @@ mod add_normalized_name_external; mod build; mod build_prefer_existing_version; mod change_rename_target; +mod cyclic_features; mod default_features; mod deprecated_default_features; mod deprecated_section; @@ -165,6 +166,12 @@ fn add_registry_packages(alt: bool) { cargo_test_support::registry::Package::new("test_nonbreaking", "0.1.1") .alternative(alt) .publish(); + cargo_test_support::registry::Package::new("test_cyclic_features", "0.1.1") + .alternative(alt) + .feature("default", &["feature-one", "feature-two"]) + .feature("feature-one", &["feature-two"]) + .feature("feature-two", &["feature-one"]) + .publish(); // Normalization cargo_test_support::registry::Package::new("linked-hash-map", "0.5.4") diff --git a/tests/testsuite/cargo_command.rs b/tests/testsuite/cargo_command.rs index f07cec332a6..62869387f2a 100644 --- a/tests/testsuite/cargo_command.rs +++ b/tests/testsuite/cargo_command.rs @@ -104,6 +104,32 @@ fn list_command_looks_at_path() { ); } +#[cfg(windows)] +#[cargo_test] +fn list_command_looks_at_path_case_mismatch() { + let proj = project() + .executable(Path::new("path-test").join("cargo-1"), "") + .build(); + + let mut path = path(); + path.push(proj.root().join("path-test")); + let path = env::join_paths(path.iter()).unwrap(); + + // See issue #11814: Environment variable names are case-insensitive on Windows. + // We need to check that having "Path" instead of "PATH" is okay. + let output = cargo_process("-v --list") + .env("Path", &path) + .env_remove("PATH") + .exec_with_output() + .unwrap(); + let output = str::from_utf8(&output.stdout).unwrap(); + assert!( + output.contains("\n 1 "), + "missing 1: {}", + output + ); +} + #[cargo_test] fn list_command_handles_known_external_commands() { let p = project() diff --git a/tests/testsuite/cargo_features.rs b/tests/testsuite/cargo_features.rs index 720d221fe1f..6e553143174 100644 --- a/tests/testsuite/cargo_features.rs +++ b/tests/testsuite/cargo_features.rs @@ -644,7 +644,10 @@ fn publish_allowed() { [FINISHED] [..] [PACKAGED] [..] [UPLOADING] a v0.0.1 [..] -[UPDATING] [..] +[UPLOADED] a v0.0.1 to registry `crates-io` +note: Waiting for `a v0.0.1` to be available at registry `crates-io`. +You may press ctrl-c to skip waiting; the crate should be available shortly. +[PUBLISHED] a v0.0.1 at registry `crates-io` ", ) .run(); diff --git a/tests/testsuite/cargo_remove/invalid_arg/stderr.log b/tests/testsuite/cargo_remove/invalid_arg/stderr.log index 82a100429ce..ac5f3cfd175 100644 --- a/tests/testsuite/cargo_remove/invalid_arg/stderr.log +++ b/tests/testsuite/cargo_remove/invalid_arg/stderr.log @@ -1,6 +1,6 @@ error: unexpected argument '--flag' found - note: to pass '--flag' as a value, use '-- --flag' + tip: to pass '--flag' as a value, use '-- --flag' Usage: cargo[EXE] remove ... diff --git a/tests/testsuite/check.rs b/tests/testsuite/check.rs index 7f2c8bc1b90..bbcf750fb2e 100644 --- a/tests/testsuite/check.rs +++ b/tests/testsuite/check.rs @@ -804,7 +804,7 @@ fn short_message_format() { .with_stderr_contains( "\ src/lib.rs:1:27: error[E0308]: mismatched types -error: could not compile `foo` due to previous error +error: could not compile `foo` (lib) due to previous error ", ) .run(); @@ -1251,7 +1251,7 @@ fn check_fixable_error_no_fix() { [CHECKING] foo v0.0.1 ([..]) {}\ [WARNING] `foo` (lib) generated 1 warning -[ERROR] could not compile `foo` due to previous error; 1 warning emitted +[ERROR] could not compile `foo` (lib) due to previous error; 1 warning emitted ", rustc_message ); @@ -1459,3 +1459,63 @@ fn check_fixable_warning_for_clippy() { .with_stderr_contains("[..] (run `cargo clippy --fix --lib -p foo` to apply 1 suggestion)") .run(); } + +#[cargo_test] +fn check_unused_manifest_keys() { + Package::new("dep", "0.1.0").publish(); + Package::new("foo", "0.1.0").publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "bar" + version = "0.2.0" + authors = [] + + [dependencies] + dep = { version = "0.1.0", wxz = "wxz" } + foo = { version = "0.1.0", abc = "abc" } + + [dev-dependencies] + foo = { version = "0.1.0", wxz = "wxz" } + + [build-dependencies] + foo = { version = "0.1.0", wxz = "wxz" } + + [target.'cfg(windows)'.dependencies] + foo = { version = "0.1.0", wxz = "wxz" } + + [target.x86_64-pc-windows-gnu.dev-dependencies] + foo = { version = "0.1.0", wxz = "wxz" } + + [target.bar.build-dependencies] + foo = { version = "0.1.0", wxz = "wxz" } + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("check") + .with_stderr( + "\ +[WARNING] unused manifest key: dependencies.dep.wxz +[WARNING] unused manifest key: dependencies.foo.abc +[WARNING] unused manifest key: dev-dependencies.foo.wxz +[WARNING] unused manifest key: build-dependencies.foo.wxz +[WARNING] unused manifest key: target.bar.build-dependencies.foo.wxz +[WARNING] unused manifest key: target.cfg(windows).dependencies.foo.wxz +[WARNING] unused manifest key: target.x86_64-pc-windows-gnu.dev-dependencies.foo.wxz +[UPDATING] `[..]` index +[DOWNLOADING] crates ... +[DOWNLOADED] foo v0.1.0 ([..]) +[DOWNLOADED] dep v0.1.0 ([..]) +[CHECKING] [..] +[CHECKING] [..] +[CHECKING] bar v0.2.0 ([CWD]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); +} diff --git a/tests/testsuite/config.rs b/tests/testsuite/config.rs index 0835c03070e..92e1f42643c 100644 --- a/tests/testsuite/config.rs +++ b/tests/testsuite/config.rs @@ -211,6 +211,31 @@ f1 = 123 assert_eq!(s, S { f1: Some(456) }); } +#[cfg(windows)] +#[cargo_test] +fn environment_variable_casing() { + // Issue #11814: Environment variable names are case-insensitive on Windows. + let config = ConfigBuilder::new() + .env("Path", "abc") + .env("Two-Words", "abc") + .env("two_words", "def") + .build(); + + let var = config.get_env("PATH").unwrap(); + assert_eq!(var, String::from("abc")); + + let var = config.get_env("path").unwrap(); + assert_eq!(var, String::from("abc")); + + let var = config.get_env("TWO-WORDS").unwrap(); + assert_eq!(var, String::from("abc")); + + // Make sure that we can still distinguish between dashes and underscores + // in variable names. + let var = config.get_env("Two_Words").unwrap(); + assert_eq!(var, String::from("def")); +} + #[cargo_test] fn config_works_with_extension() { write_config_toml( diff --git a/tests/testsuite/credential_process.rs b/tests/testsuite/credential_process.rs index 0d174b6e379..8c202c6a3ca 100644 --- a/tests/testsuite/credential_process.rs +++ b/tests/testsuite/credential_process.rs @@ -130,7 +130,10 @@ Only one of these values may be set, remove one or the other to proceed. [PACKAGING] foo v0.1.0 [..] [PACKAGED] [..] [UPLOADING] foo v0.1.0 [..] -[UPDATING] [..] +[UPLOADED] foo v0.1.0 [..] +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] foo v0.1.0 [..] ", ) .run(); @@ -222,7 +225,10 @@ fn publish() { [PACKAGING] foo v0.1.0 [..] [PACKAGED] [..] [UPLOADING] foo v0.1.0 [..] -[UPDATING] [..] +[UPLOADED] foo v0.1.0 [..] +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] foo v0.1.0 [..] ", ) .run(); @@ -262,7 +268,7 @@ the credential-process configuration value must pass the \ cargo_process("logout -Z credential-process") .replace_crates_io(registry.index_url()) - .masquerade_as_nightly_cargo(&["credential-process", "cargo-logout"]) + .masquerade_as_nightly_cargo(&["credential-process"]) .with_status(101) .with_stderr( "\ @@ -371,12 +377,15 @@ fn logout() { .unwrap(); cargo_process("logout -Z credential-process") - .masquerade_as_nightly_cargo(&["credential-process", "cargo-logout"]) + .masquerade_as_nightly_cargo(&["credential-process"]) .replace_crates_io(server.index_url()) .with_stderr( "\ token for `crates-io` has been erased! [LOGOUT] token for `crates-io` has been removed from local storage +[NOTE] This does not revoke the token on the registry server. + If you need to revoke the token, visit \ + and follow the instructions there. ", ) .run(); diff --git a/tests/testsuite/cross_publish.rs b/tests/testsuite/cross_publish.rs index 5b1416ef072..83e0ecab709 100644 --- a/tests/testsuite/cross_publish.rs +++ b/tests/testsuite/cross_publish.rs @@ -112,7 +112,10 @@ fn publish_with_target() { [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] [PACKAGED] [..] [UPLOADING] foo v0.0.0 ([CWD]) -[UPDATING] crates.io index +[UPLOADED] foo v0.0.0 to registry `crates-io` +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] foo v0.0.0 at registry `crates-io` ", ) .run(); diff --git a/tests/testsuite/direct_minimal_versions.rs b/tests/testsuite/direct_minimal_versions.rs new file mode 100644 index 00000000000..0e62d6ce083 --- /dev/null +++ b/tests/testsuite/direct_minimal_versions.rs @@ -0,0 +1,236 @@ +//! Tests for minimal-version resolution. +//! +//! Note: Some tests are located in the resolver-tests package. + +use cargo_test_support::project; +use cargo_test_support::registry::Package; + +#[cargo_test] +fn simple() { + Package::new("dep", "1.0.0").publish(); + Package::new("dep", "1.1.0").publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + authors = [] + version = "0.0.1" + + [dependencies] + dep = "1.0" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("generate-lockfile -Zdirect-minimal-versions") + .masquerade_as_nightly_cargo(&["direct-minimal-versions"]) + .run(); + + let lock = p.read_lockfile(); + + assert!( + lock.contains("1.0.0"), + "dep minimal version must be present" + ); + assert!( + !lock.contains("1.1.0"), + "dep maximimal version cannot be present" + ); +} + +#[cargo_test] +fn mixed_dependencies() { + Package::new("dep", "1.0.0").publish(); + Package::new("dep", "1.1.0").publish(); + Package::new("dep", "1.2.0").publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + authors = [] + version = "0.0.1" + + [dependencies] + dep = "1.0" + + [dev-dependencies] + dep = "1.1" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("generate-lockfile -Zdirect-minimal-versions") + .masquerade_as_nightly_cargo(&["direct-minimal-versions"]) + .with_status(101) + .with_stderr( + r#"[UPDATING] [..] +[ERROR] failed to select a version for `dep`. + ... required by package `foo v0.0.1 ([CWD])` +versions that meet the requirements `^1.1` are: 1.1.0 + +all possible versions conflict with previously selected packages. + + previously selected package `dep v1.0.0` + ... which satisfies dependency `dep = "^1.0"` of package `foo v0.0.1 ([CWD])` + +failed to select a version for `dep` which could resolve this conflict +"#, + ) + .run(); +} + +#[cargo_test] +fn yanked() { + Package::new("dep", "1.0.0").yanked(true).publish(); + Package::new("dep", "1.1.0").publish(); + Package::new("dep", "1.2.0").publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + authors = [] + version = "0.0.1" + + [dependencies] + dep = "1.0" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("generate-lockfile -Zdirect-minimal-versions") + .masquerade_as_nightly_cargo(&["direct-minimal-versions"]) + .run(); + + let lock = p.read_lockfile(); + + assert!( + lock.contains("1.1.0"), + "dep minimal version must be present" + ); + assert!( + !lock.contains("1.0.0"), + "yanked minimal version must be skipped" + ); + assert!( + !lock.contains("1.2.0"), + "dep maximimal version cannot be present" + ); +} + +#[cargo_test] +fn indirect() { + Package::new("indirect", "2.0.0").publish(); + Package::new("indirect", "2.1.0").publish(); + Package::new("indirect", "2.2.0").publish(); + Package::new("direct", "1.0.0") + .dep("indirect", "2.1") + .publish(); + Package::new("direct", "1.1.0") + .dep("indirect", "2.1") + .publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + authors = [] + version = "0.0.1" + + [dependencies] + direct = "1.0" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("generate-lockfile -Zdirect-minimal-versions") + .masquerade_as_nightly_cargo(&["direct-minimal-versions"]) + .run(); + + let lock = p.read_lockfile(); + + assert!( + lock.contains("1.0.0"), + "direct minimal version must be present" + ); + assert!( + !lock.contains("1.1.0"), + "direct maximimal version cannot be present" + ); + assert!( + !lock.contains("2.0.0"), + "indirect minimal version cannot be present" + ); + assert!( + !lock.contains("2.1.0"), + "indirect minimal version cannot be present" + ); + assert!( + lock.contains("2.2.0"), + "indirect maximal version must be present" + ); +} + +#[cargo_test] +fn indirect_conflict() { + Package::new("indirect", "2.0.0").publish(); + Package::new("indirect", "2.1.0").publish(); + Package::new("indirect", "2.2.0").publish(); + Package::new("direct", "1.0.0") + .dep("indirect", "2.1") + .publish(); + Package::new("direct", "1.1.0") + .dep("indirect", "2.1") + .publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + authors = [] + version = "0.0.1" + + [dependencies] + direct = "1.0" + indirect = "2.0" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("generate-lockfile -Zdirect-minimal-versions") + .masquerade_as_nightly_cargo(&["direct-minimal-versions"]) + .with_status(101) + .with_stderr( + r#"[UPDATING] [..] +[ERROR] failed to select a version for `indirect`. + ... required by package `direct v1.0.0` + ... which satisfies dependency `direct = "^1.0"` of package `foo v0.0.1 ([CWD])` +versions that meet the requirements `^2.1` are: 2.2.0, 2.1.0 + +all possible versions conflict with previously selected packages. + + previously selected package `indirect v2.0.0` + ... which satisfies dependency `indirect = "^2.0"` of package `foo v0.0.1 ([CWD])` + +failed to select a version for `indirect` which could resolve this conflict +"#, + ) + .run(); +} diff --git a/tests/testsuite/features_namespaced.rs b/tests/testsuite/features_namespaced.rs index 26c4d0ac53e..8ec2fc2e35d 100644 --- a/tests/testsuite/features_namespaced.rs +++ b/tests/testsuite/features_namespaced.rs @@ -894,7 +894,10 @@ fn publish_no_implicit() { [PACKAGING] foo v0.1.0 [..] [PACKAGED] [..] [UPLOADING] foo v0.1.0 [..] -[UPDATING] [..] +[UPLOADED] foo v0.1.0 [..] +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] foo v0.1.0 [..] ", ) .run(); @@ -1013,7 +1016,10 @@ fn publish() { [FINISHED] [..] [PACKAGED] [..] [UPLOADING] foo v0.1.0 [..] -[UPDATING] [..] +[UPLOADED] foo v0.1.0 [..] +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] foo v0.1.0 [..] ", ) .run(); diff --git a/tests/testsuite/fix.rs b/tests/testsuite/fix.rs index 17bcd111ce7..54a021c03c9 100644 --- a/tests/testsuite/fix.rs +++ b/tests/testsuite/fix.rs @@ -29,7 +29,7 @@ fn do_not_fix_broken_builds() { p.cargo("fix --allow-no-vcs") .env("__CARGO_FIX_YOLO", "1") .with_status(101) - .with_stderr_contains("[ERROR] could not compile `foo` due to previous error") + .with_stderr_contains("[ERROR] could not compile `foo` (lib) due to previous error") .run(); assert!(p.read_file("src/lib.rs").contains("let mut x = 3;")); } diff --git a/tests/testsuite/generate_lockfile.rs b/tests/testsuite/generate_lockfile.rs index 74f6e78cc9a..d2b63360533 100644 --- a/tests/testsuite/generate_lockfile.rs +++ b/tests/testsuite/generate_lockfile.rs @@ -1,6 +1,6 @@ //! Tests for the `cargo generate-lockfile` command. -use cargo_test_support::registry::Package; +use cargo_test_support::registry::{Package, RegistryBuilder}; use cargo_test_support::{basic_manifest, paths, project, ProjectBuilder}; use std::fs; @@ -57,6 +57,16 @@ fn adding_and_removing_packages() { } #[cargo_test] +fn no_index_update_sparse() { + let _registry = RegistryBuilder::new().http_index().build(); + no_index_update(); +} + +#[cargo_test] +fn no_index_update_git() { + no_index_update(); +} + fn no_index_update() { Package::new("serde", "1.0.0").publish(); diff --git a/tests/testsuite/git.rs b/tests/testsuite/git.rs index 48e3eaaba20..b170c204f75 100644 --- a/tests/testsuite/git.rs +++ b/tests/testsuite/git.rs @@ -9,6 +9,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::thread; +use cargo_test_support::git::cargo_uses_gitoxide; use cargo_test_support::paths::{self, CargoPathExt}; use cargo_test_support::registry::Package; use cargo_test_support::{basic_lib_manifest, basic_manifest, git, main_file, path2url, project}; @@ -1827,6 +1828,51 @@ fn fetch_downloads() { p.cargo("fetch").with_stdout("").run(); } +#[cargo_test] +fn fetch_downloads_with_git2_first_then_with_gitoxide_and_vice_versa() { + let bar = git::new("bar", |project| { + project + .file("Cargo.toml", &basic_manifest("bar", "0.5.0")) + .file("src/lib.rs", "pub fn bar() -> i32 { 1 }") + }); + let feature_configuration = if cargo_uses_gitoxide() { + // When we are always using `gitoxide` by default, create the registry with git2 as well as the download… + "-Zgitoxide=internal-use-git2" + } else { + // …otherwise create the registry and the git download with `gitoxide`. + "-Zgitoxide=fetch" + }; + + let p = project() + .file( + "Cargo.toml", + &format!( + r#" + [package] + name = "foo" + version = "0.5.0" + authors = [] + [dependencies.bar] + git = '{url}' + "#, + url = bar.url() + ), + ) + .file("src/main.rs", "fn main() {}") + .build(); + p.cargo("fetch") + .arg(feature_configuration) + .masquerade_as_nightly_cargo(&["unstable features must be available for -Z gitoxide"]) + .with_stderr(&format!( + "[UPDATING] git repository `{url}`", + url = bar.url() + )) + .run(); + + Package::new("bar", "1.0.0").publish(); // trigger a crates-index change. + p.cargo("fetch").with_stdout("").run(); +} + #[cargo_test] fn warnings_in_git_dep() { let bar = git::new("bar", |project| { diff --git a/tests/testsuite/git_auth.rs b/tests/testsuite/git_auth.rs index 9f2bece8b8d..b6e68fa3d88 100644 --- a/tests/testsuite/git_auth.rs +++ b/tests/testsuite/git_auth.rs @@ -8,6 +8,7 @@ use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; use std::sync::Arc; use std::thread::{self, JoinHandle}; +use cargo_test_support::git::cargo_uses_gitoxide; use cargo_test_support::paths; use cargo_test_support::{basic_manifest, project}; @@ -104,6 +105,11 @@ fn setup_failed_auth_test() -> (SocketAddr, JoinHandle<()>, Arc) { // Tests that HTTP auth is offered from `credential.helper`. #[cargo_test] fn http_auth_offered() { + // TODO(Seb): remove this once possible. + if cargo_uses_gitoxide() { + // Without the fixes in https://github.com/Byron/gitoxide/releases/tag/gix-v0.41.0 this test is flaky. + return; + } let (addr, t, connections) = setup_failed_auth_test(); let p = project() .file( @@ -157,8 +163,7 @@ Caused by: https://[..] Caused by: -", - addr = addr +" )) .run(); @@ -206,15 +211,16 @@ fn https_something_happens() { p.cargo("check -v") .with_status(101) .with_stderr_contains(&format!( - "[UPDATING] git repository `https://{addr}/foo/bar`", - addr = addr + "[UPDATING] git repository `https://{addr}/foo/bar`" )) .with_stderr_contains(&format!( "\ Caused by: {errmsg} ", - errmsg = if cfg!(windows) { + errmsg = if cargo_uses_gitoxide() { + "[..]SSL connect error [..]" + } else if cfg!(windows) { "[..]failed to send request: [..]" } else if cfg!(target_os = "macos") { // macOS is difficult to tests as some builds may use Security.framework, @@ -258,18 +264,40 @@ fn ssh_something_happens() { .file("src/main.rs", "") .build(); - p.cargo("check -v") - .with_status(101) - .with_stderr_contains(&format!( - "[UPDATING] git repository `ssh://{addr}/foo/bar`", - addr = addr - )) - .with_stderr_contains( + let (expected_ssh_message, expected_update) = if cargo_uses_gitoxide() { + // Due to the usage of `ssh` and `ssh.exe` respectively, the messages change. + // This will be adjusted to use `ssh2` to get rid of this dependency and have uniform messaging. + let message = if cfg!(windows) { + // The order of multiple possible messages isn't deterministic within `ssh`, and `gitoxide` detects both + // but gets to report only the first. Thus this test can flip-flop from one version of the error to the other + // and we can't test for that. + // We'd want to test for: + // "[..]ssh: connect to host 127.0.0.1 [..]" + // ssh: connect to host example.org port 22: No route to host + // "[..]banner exchange: Connection to 127.0.0.1 [..]" + // banner exchange: Connection to 127.0.0.1 port 62250: Software caused connection abort + // But since there is no common meaningful sequence or word, we can only match a small telling sequence of characters. + "[..]onnect[..]" + } else { + "[..]Connection [..] by [..]" + }; + ( + message, + format!("[..]Unable to update ssh://{addr}/foo/bar"), + ) + } else { + ( "\ Caused by: [..]failed to start SSH session: Failed getting banner[..] ", + format!("[UPDATING] git repository `ssh://{addr}/foo/bar`"), ) + }; + p.cargo("check -v") + .with_status(101) + .with_stderr_contains(&expected_update) + .with_stderr_contains(expected_ssh_message) .run(); t.join().ok().unwrap(); } @@ -294,11 +322,12 @@ fn net_err_suggests_fetch_with_cli() { p.cargo("check -v") .with_status(101) - .with_stderr( + .with_stderr(format!( "\ [UPDATING] git repository `ssh://needs-proxy.invalid/git` warning: spurious network error[..] warning: spurious network error[..] +warning: spurious network error[..] [ERROR] failed to get `foo` as a dependency of package `foo v0.0.0 [..]` Caused by: @@ -316,9 +345,14 @@ Caused by: https://[..] Caused by: - failed to resolve address for needs-proxy.invalid[..] + {trailer} ", - ) + trailer = if cargo_uses_gitoxide() { + "An IO error occurred when talking to the server\n\nCaused by:\n ssh: Could not resolve hostname needs-proxy.invalid[..]" + } else { + "failed to resolve address for needs-proxy.invalid[..]" + } + )) .run(); p.change_file( @@ -338,6 +372,11 @@ Caused by: #[cargo_test] fn instead_of_url_printed() { + // TODO(Seb): remove this once possible. + if cargo_uses_gitoxide() { + // Without the fixes in https://github.com/Byron/gitoxide/releases/tag/gix-v0.41.0 this test is flaky. + return; + } let (addr, t, _connections) = setup_failed_auth_test(); let config = paths::home().join(".gitconfig"); let mut config = git2::Config::open(&config).unwrap(); @@ -389,8 +428,8 @@ Caused by: Caused by: [..] -", - addr = addr +{trailer}", + trailer = if cargo_uses_gitoxide() { "\nCaused by:\n [..]" } else { "" } )) .run(); diff --git a/tests/testsuite/git_gc.rs b/tests/testsuite/git_gc.rs index 4a8228f87c9..fd4fe30a979 100644 --- a/tests/testsuite/git_gc.rs +++ b/tests/testsuite/git_gc.rs @@ -5,6 +5,7 @@ use std::ffi::OsStr; use std::path::PathBuf; use cargo_test_support::git; +use cargo_test_support::git::cargo_uses_gitoxide; use cargo_test_support::paths; use cargo_test_support::project; use cargo_test_support::registry::Package; @@ -96,6 +97,11 @@ fn use_git_gc() { #[cargo_test] fn avoid_using_git() { + if cargo_uses_gitoxide() { + // file protocol without git binary is currently not possible - needs built-in upload-pack. + // See https://github.com/Byron/gitoxide/issues/734 (support for the file protocol) progress updates. + return; + } let path = env::var_os("PATH").unwrap_or_default(); let mut paths = env::split_paths(&path).collect::>(); let idx = paths diff --git a/tests/testsuite/https.rs b/tests/testsuite/https.rs index c7aec911105..501eeae0558 100644 --- a/tests/testsuite/https.rs +++ b/tests/testsuite/https.rs @@ -30,7 +30,7 @@ fn self_signed_should_fail() { .build(); // I think the text here depends on the curl backend. let err_msg = if cfg!(target_os = "macos") { - "untrusted connection error; class=Ssl (16); code=Certificate (-17)" + "unexpected return value from ssl handshake -9806; class=Ssl (16)" } else if cfg!(unix) { "the SSL certificate is invalid; class=Ssl (16); code=Certificate (-17)" } else if cfg!(windows) { diff --git a/tests/testsuite/inheritable_workspace_fields.rs b/tests/testsuite/inheritable_workspace_fields.rs index d32419d7e69..92c96b985a5 100644 --- a/tests/testsuite/inheritable_workspace_fields.rs +++ b/tests/testsuite/inheritable_workspace_fields.rs @@ -172,7 +172,10 @@ fn inherit_own_workspace_fields() { [FINISHED] [..] [PACKAGED] [..] [UPLOADING] foo v1.2.3 [..] -[UPDATING] [..] +[UPLOADED] foo v1.2.3 to registry `crates-io` +note: Waiting for `foo v1.2.3` to be available at registry `crates-io`. +You may press ctrl-c to skip waiting; the crate should be available shortly. +[PUBLISHED] foo v1.2.3 at registry `crates-io` ", ) .run(); @@ -318,7 +321,10 @@ fn inherit_own_dependencies() { [FINISHED] [..] [PACKAGED] [..] [UPLOADING] bar v0.2.0 [..] -[UPDATING] [..] +[UPLOADED] bar v0.2.0 to registry `crates-io` +note: Waiting for `bar v0.2.0` to be available at registry `crates-io`. +You may press ctrl-c to skip waiting; the crate should be available shortly. +[PUBLISHED] bar v0.2.0 at registry `crates-io` ", ) .run(); @@ -460,7 +466,10 @@ fn inherit_own_detailed_dependencies() { [FINISHED] [..] [PACKAGED] [..] [UPLOADING] bar v0.2.0 [..] -[UPDATING] [..] +[UPLOADED] bar v0.2.0 to registry `crates-io` +note: Waiting for `bar v0.2.0` to be available at registry `crates-io`. +You may press ctrl-c to skip waiting; the crate should be available shortly. +[PUBLISHED] bar v0.2.0 at registry `crates-io` ", ) .run(); @@ -696,7 +705,10 @@ fn inherit_workspace_fields() { [FINISHED] [..] [PACKAGED] [..] [UPLOADING] bar v1.2.3 [..] -[UPDATING] [..] +[UPLOADED] bar v1.2.3 to registry `crates-io` +note: Waiting for `bar v1.2.3` to be available at registry `crates-io`. +You may press ctrl-c to skip waiting; the crate should be available shortly. +[PUBLISHED] bar v1.2.3 at registry `crates-io` ", ) .run(); @@ -850,7 +862,10 @@ fn inherit_dependencies() { [FINISHED] [..] [PACKAGED] [..] [UPLOADING] bar v0.2.0 [..] -[UPDATING] [..] +[UPLOADED] bar v0.2.0 to registry `crates-io` +note: Waiting for `bar v0.2.0` to be available at registry `crates-io`. +You may press ctrl-c to skip waiting; the crate should be available shortly. +[PUBLISHED] bar v0.2.0 at registry `crates-io` ", ) .run(); @@ -1215,7 +1230,7 @@ fn error_workspace_false() { Caused by: `workspace` cannot be false - in `package.description` + in `package.description.workspace` ", ) .run(); @@ -1253,8 +1268,8 @@ fn error_workspace_dependency_looked_for_workspace_itself() { .with_status(101) .with_stderr( "\ -[WARNING] [CWD]/Cargo.toml: dependency (dep) specified without providing a local path, Git repository, or version to use. This will be considered an error in future versions [WARNING] [CWD]/Cargo.toml: unused manifest key: workspace.dependencies.dep.workspace +[WARNING] [CWD]/Cargo.toml: dependency (dep) specified without providing a local path, Git repository, or version to use. This will be considered an error in future versions [UPDATING] `dummy-registry` index [ERROR] no matching package named `dep` found location searched: registry `crates-io` @@ -1573,8 +1588,8 @@ fn cannot_inherit_in_patch() { .with_status(101) .with_stderr( "\ -[WARNING] [CWD]/Cargo.toml: dependency (bar) specified without providing a local path, Git repository, or version to use. This will be considered an error in future versions [WARNING] [CWD]/Cargo.toml: unused manifest key: patch.crates-io.bar.workspace +[WARNING] [CWD]/Cargo.toml: dependency (bar) specified without providing a local path, Git repository, or version to use. This will be considered an error in future versions [UPDATING] `dummy-registry` index [ERROR] failed to resolve patches for `https://github.com/rust-lang/crates.io-index` @@ -1584,3 +1599,119 @@ Caused by: ) .run(); } + +#[cargo_test] +fn warn_inherit_unused_manifest_key_dep() { + Package::new("dep", "0.1.0").publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = [] + [workspace.dependencies] + dep = { version = "0.1", wxz = "wxz" } + + [package] + name = "bar" + version = "0.2.0" + authors = [] + + [dependencies] + dep = { workspace = true, wxz = "wxz" } + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("check") + .with_stderr( + "\ +[WARNING] [CWD]/Cargo.toml: unused manifest key: workspace.dependencies.dep.wxz +[WARNING] [CWD]/Cargo.toml: unused manifest key: dependencies.dep.wxz +[UPDATING] `[..]` index +[DOWNLOADING] crates ... +[DOWNLOADED] dep v0.1.0 ([..]) +[CHECKING] [..] +[CHECKING] bar v0.2.0 ([CWD]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); +} + +#[cargo_test] +fn warn_inherit_unused_manifest_key_package() { + Package::new("dep", "0.1.0").publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + badges = { workspace = true, xyz = "abc"} + + [workspace] + members = [] + [workspace.package] + version = "1.2.3" + authors = ["Rustaceans"] + description = "This is a crate" + documentation = "https://www.rust-lang.org/learn" + homepage = "https://www.rust-lang.org" + repository = "https://github.com/example/example" + license = "MIT" + keywords = ["cli"] + categories = ["development-tools"] + publish = true + edition = "2018" + rust-version = "1.60" + exclude = ["foo.txt"] + include = ["bar.txt", "**/*.rs", "Cargo.toml"] + [workspace.package.badges] + gitlab = { repository = "https://gitlab.com/rust-lang/rust", branch = "master" } + + [package] + name = "bar" + version = { workspace = true, xyz = "abc"} + authors = { workspace = true, xyz = "abc"} + description = { workspace = true, xyz = "abc"} + documentation = { workspace = true, xyz = "abc"} + homepage = { workspace = true, xyz = "abc"} + repository = { workspace = true, xyz = "abc"} + license = { workspace = true, xyz = "abc"} + keywords = { workspace = true, xyz = "abc"} + categories = { workspace = true, xyz = "abc"} + publish = { workspace = true, xyz = "abc"} + edition = { workspace = true, xyz = "abc"} + rust-version = { workspace = true, xyz = "abc"} + exclude = { workspace = true, xyz = "abc"} + include = { workspace = true, xyz = "abc"} + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("check") + .with_stderr( + "\ +[WARNING] [CWD]/Cargo.toml: unused manifest key: package.authors.xyz +[WARNING] [CWD]/Cargo.toml: unused manifest key: package.categories.xyz +[WARNING] [CWD]/Cargo.toml: unused manifest key: package.description.xyz +[WARNING] [CWD]/Cargo.toml: unused manifest key: package.documentation.xyz +[WARNING] [CWD]/Cargo.toml: unused manifest key: package.edition.xyz +[WARNING] [CWD]/Cargo.toml: unused manifest key: package.exclude.xyz +[WARNING] [CWD]/Cargo.toml: unused manifest key: package.homepage.xyz +[WARNING] [CWD]/Cargo.toml: unused manifest key: package.include.xyz +[WARNING] [CWD]/Cargo.toml: unused manifest key: package.keywords.xyz +[WARNING] [CWD]/Cargo.toml: unused manifest key: package.license.xyz +[WARNING] [CWD]/Cargo.toml: unused manifest key: package.publish.xyz +[WARNING] [CWD]/Cargo.toml: unused manifest key: package.repository.xyz +[WARNING] [CWD]/Cargo.toml: unused manifest key: package.rust-version.xyz +[WARNING] [CWD]/Cargo.toml: unused manifest key: package.version.xyz +[CHECKING] bar v1.2.3 ([CWD]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); +} diff --git a/tests/testsuite/init/mercurial_autodetect/out/.hgignore b/tests/testsuite/init/mercurial_autodetect/out/.hgignore index 1ae6a78bbc9..0cc56e7ff73 100644 --- a/tests/testsuite/init/mercurial_autodetect/out/.hgignore +++ b/tests/testsuite/init/mercurial_autodetect/out/.hgignore @@ -1,2 +1,2 @@ -^target/ +^target$ ^Cargo.lock$ diff --git a/tests/testsuite/init/simple_hg/out/.hgignore b/tests/testsuite/init/simple_hg/out/.hgignore index 1ae6a78bbc9..0cc56e7ff73 100644 --- a/tests/testsuite/init/simple_hg/out/.hgignore +++ b/tests/testsuite/init/simple_hg/out/.hgignore @@ -1,2 +1,2 @@ -^target/ +^target$ ^Cargo.lock$ diff --git a/tests/testsuite/init/simple_hg_ignore_exists/out/.hgignore b/tests/testsuite/init/simple_hg_ignore_exists/out/.hgignore index 2ab1fce0101..dd9ddffeb23 100644 --- a/tests/testsuite/init/simple_hg_ignore_exists/out/.hgignore +++ b/tests/testsuite/init/simple_hg_ignore_exists/out/.hgignore @@ -2,5 +2,5 @@ # Added by cargo -^target/ +^target$ ^Cargo.lock$ diff --git a/tests/testsuite/init/unknown_flags/stderr.log b/tests/testsuite/init/unknown_flags/stderr.log index 31e7eb84d8c..980e8acd82a 100644 --- a/tests/testsuite/init/unknown_flags/stderr.log +++ b/tests/testsuite/init/unknown_flags/stderr.log @@ -1,6 +1,6 @@ error: unexpected argument '--flag' found - note: to pass '--flag' as a value, use '-- --flag' + tip: to pass '--flag' as a value, use '-- --flag' Usage: cargo[EXE] init diff --git a/tests/testsuite/install.rs b/tests/testsuite/install.rs index b06f3d3818e..dd9844f170b 100644 --- a/tests/testsuite/install.rs +++ b/tests/testsuite/install.rs @@ -560,7 +560,7 @@ Available binaries: } #[cargo_test] -fn multiple_crates_error() { +fn multiple_packages_containing_binaries() { let p = git::repo(&paths::root().join("foo")) .file("Cargo.toml", &basic_manifest("foo", "0.1.0")) .file("src/main.rs", "fn main() {}") @@ -568,20 +568,101 @@ fn multiple_crates_error() { .file("a/src/main.rs", "fn main() {}") .build(); + let git_url = p.url().to_string(); cargo_process("install --git") .arg(p.url().to_string()) .with_status(101) - .with_stderr( + .with_stderr(format!( "\ [UPDATING] git repository [..] [ERROR] multiple packages with binaries found: bar, foo. \ -When installing a git repository, cargo will always search the entire repo for any Cargo.toml. \ -Please specify which to install. -", - ) +When installing a git repository, cargo will always search the entire repo for any Cargo.toml. +Please specify a package, e.g. `cargo install --git {git_url} bar`. +" + )) + .run(); +} + +#[cargo_test] +fn multiple_packages_matching_example() { + let p = git::repo(&paths::root().join("foo")) + .file("Cargo.toml", &basic_manifest("foo", "0.1.0")) + .file("src/lib.rs", "") + .file("examples/ex1.rs", "fn main() {}") + .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) + .file("bar/src/lib.rs", "") + .file("bar/examples/ex1.rs", "fn main() {}") + .build(); + + let git_url = p.url().to_string(); + cargo_process("install --example ex1 --git") + .arg(p.url().to_string()) + .with_status(101) + .with_stderr(format!( + "\ +[UPDATING] git repository [..] +[ERROR] multiple packages with examples found: bar, foo. \ +When installing a git repository, cargo will always search the entire repo for any Cargo.toml. +Please specify a package, e.g. `cargo install --git {git_url} bar`." + )) .run(); } +#[cargo_test] +fn multiple_binaries_deep_select_uses_package_name() { + let p = git::repo(&paths::root().join("foo")) + .file("Cargo.toml", &basic_manifest("foo", "0.1.0")) + .file("src/main.rs", "fn main() {}") + .file("bar/baz/Cargo.toml", &basic_manifest("baz", "0.1.0")) + .file("bar/baz/src/main.rs", "fn main() {}") + .build(); + + cargo_process("install --git") + .arg(p.url().to_string()) + .arg("baz") + .run(); +} + +#[cargo_test] +fn multiple_binaries_in_selected_package_installs_all() { + let p = git::repo(&paths::root().join("foo")) + .file("Cargo.toml", &basic_manifest("foo", "0.1.0")) + .file("src/lib.rs", "") + .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) + .file("bar/src/bin/bin1.rs", "fn main() {}") + .file("bar/src/bin/bin2.rs", "fn main() {}") + .build(); + + cargo_process("install --git") + .arg(p.url().to_string()) + .arg("bar") + .run(); + + let cargo_home = cargo_home(); + assert_has_installed_exe(&cargo_home, "bin1"); + assert_has_installed_exe(&cargo_home, "bin2"); +} + +#[cargo_test] +fn multiple_binaries_in_selected_package_with_bin_option_installs_only_one() { + let p = git::repo(&paths::root().join("foo")) + .file("Cargo.toml", &basic_manifest("foo", "0.1.0")) + .file("src/lib.rs", "") + .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) + .file("bar/src/bin/bin1.rs", "fn main() {}") + .file("bar/src/bin/bin2.rs", "fn main() {}") + .build(); + + cargo_process("install --bin bin1 --git") + .arg(p.url().to_string()) + .arg("bar") + .run(); + + let cargo_home = cargo_home(); + assert_has_installed_exe(&cargo_home, "bin1"); + assert_has_not_installed_exe(&cargo_home, "bin2"); +} + #[cargo_test] fn multiple_crates_select() { let p = git::repo(&paths::root().join("foo")) @@ -881,7 +962,7 @@ fn compile_failure() { .with_status(101) .with_stderr_contains( "\ -[ERROR] could not compile `foo` due to previous error +[ERROR] could not compile `foo` (bin \"foo\") due to previous error [ERROR] failed to compile `foo v0.0.1 ([..])`, intermediate artifacts can be \ found at `[..]target` ", diff --git a/tests/testsuite/login.rs b/tests/testsuite/login.rs index 19387aed57c..85b299f282a 100644 --- a/tests/testsuite/login.rs +++ b/tests/testsuite/login.rs @@ -27,14 +27,15 @@ fn setup_new_credentials_at(config: PathBuf) { )); } -fn check_token(expected_token: &str, registry: Option<&str>) -> bool { +/// Asserts whether or not the token is set to the given value for the given registry. +pub fn check_token(expected_token: Option<&str>, registry: Option<&str>) { let credentials = credentials_toml(); assert!(credentials.is_file()); let contents = fs::read_to_string(&credentials).unwrap(); let toml: toml::Table = contents.parse().unwrap(); - let token = match registry { + let actual_token = match registry { // A registry has been provided, so check that the token exists in a // table for the registry. Some(registry) => toml @@ -54,10 +55,15 @@ fn check_token(expected_token: &str, registry: Option<&str>) -> bool { }), }; - if let Some(token_val) = token { - token_val == expected_token - } else { - false + match (actual_token, expected_token) { + (None, None) => {} + (Some(actual), Some(expected)) => assert_eq!(actual, expected), + (None, Some(expected)) => { + panic!("expected `{registry:?}` to be `{expected}`, but was not set") + } + (Some(actual), None) => { + panic!("expected `{registry:?}` to be unset, but was set to `{actual}`") + } } } @@ -75,10 +81,10 @@ fn registry_credentials() { cargo_process("login --registry").arg(reg).arg(TOKEN).run(); // Ensure that we have not updated the default token - assert!(check_token(ORIGINAL_TOKEN, None)); + check_token(Some(ORIGINAL_TOKEN), None); // Also ensure that we get the new token for the registry - assert!(check_token(TOKEN, Some(reg))); + check_token(Some(TOKEN), Some(reg)); let reg2 = "alternative2"; cargo_process("login --registry") @@ -88,9 +94,9 @@ fn registry_credentials() { // Ensure not overwriting 1st alternate registry token with // 2nd alternate registry token (see rust-lang/cargo#7701). - assert!(check_token(ORIGINAL_TOKEN, None)); - assert!(check_token(TOKEN, Some(reg))); - assert!(check_token(TOKEN2, Some(reg2))); + check_token(Some(ORIGINAL_TOKEN), None); + check_token(Some(TOKEN), Some(reg)); + check_token(Some(TOKEN2), Some(reg2)); } #[cargo_test] @@ -134,40 +140,45 @@ fn invalid_login_token() { .build(); setup_new_credentials(); - let check = |stdin: &str, stderr: &str| { + let check = |stdin: &str, stderr: &str, status: i32| { cargo_process("login") .replace_crates_io(registry.index_url()) .with_stdout("please paste the token found on [..]/me below") .with_stdin(stdin) .with_stderr(stderr) - .with_status(101) + .with_status(status) .run(); }; - check( - "😄", - "\ -[UPDATING] crates.io index -[ERROR] token contains invalid characters. -Only printable ISO-8859-1 characters are allowed as it is sent in a HTTPS header.", - ); - check( - "\u{0016}", - "\ -[ERROR] token contains invalid characters. + let invalid = |stdin: &str| { + check( + stdin, + "[ERROR] token contains invalid characters. Only printable ISO-8859-1 characters are allowed as it is sent in a HTTPS header.", - ); + 101, + ) + }; + let valid = |stdin: &str| check(stdin, "[LOGIN] token for `crates.io` saved", 0); + + // Update config.json so that the rest of the tests don't need to care + // whether or not `Updating` is printed. check( - "\u{0000}", + "test", "\ -[ERROR] token contains invalid characters. -Only printable ISO-8859-1 characters are allowed as it is sent in a HTTPS header.", +[UPDATING] crates.io index +[LOGIN] token for `crates.io` saved +", + 0, ); - check( - "你好", - "\ -[ERROR] token contains invalid characters. -Only printable ISO-8859-1 characters are allowed as it is sent in a HTTPS header.", + + invalid("😄"); + invalid("\u{0016}"); + invalid("\u{0000}"); + invalid("你好"); + valid("foo\tbar"); + valid("foo bar"); + valid( + r##"!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~"##, ); } @@ -362,3 +373,32 @@ fn login_with_generate_asymmetric_token() { let credentials = fs::read_to_string(&credentials).unwrap(); assert!(credentials.contains("secret-key = \"k3.secret.")); } + +#[cargo_test] +fn default_registry_configured() { + // When registry.default is set, login should use that one when + // --registry is not used. + let _alternative = RegistryBuilder::new().alternative().build(); + let cargo_home = paths::home().join(".cargo"); + cargo_util::paths::append( + &cargo_home.join("config"), + br#" + [registry] + default = "alternative" + "#, + ) + .unwrap(); + + cargo_process("login") + .arg("a-new-token") + .with_stderr( + "\ +[UPDATING] `alternative` index +[LOGIN] token for `alternative` saved +", + ) + .run(); + + check_token(None, None); + check_token(Some("a-new-token"), Some("alternative")); +} diff --git a/tests/testsuite/logout.rs b/tests/testsuite/logout.rs index 92b6f42a433..7b5e10de2bf 100644 --- a/tests/testsuite/logout.rs +++ b/tests/testsuite/logout.rs @@ -1,91 +1,104 @@ //! Tests for the `cargo logout` command. -use cargo_test_support::install::cargo_home; +use super::login::check_token; +use cargo_test_support::paths::{self, CargoPathExt}; use cargo_test_support::registry::TestRegistry; use cargo_test_support::{cargo_process, registry}; -use std::fs; -#[cargo_test] -fn gated() { - registry::init(); - cargo_process("logout") - .masquerade_as_nightly_cargo(&["cargo-logout"]) - .with_status(101) - .with_stderr( - "\ -[ERROR] the `cargo logout` command is unstable, pass `-Z unstable-options` to enable it -See https://github.com/rust-lang/cargo/issues/8933 for more information about \ -the `cargo logout` command. -", - ) - .run(); -} - -/// Checks whether or not the token is set for the given token. -fn check_config_token(registry: Option<&str>, should_be_set: bool) { - let credentials = cargo_home().join("credentials.toml"); - let contents = fs::read_to_string(&credentials).unwrap(); - let toml: toml::Table = contents.parse().unwrap(); - if let Some(registry) = registry { - assert_eq!( - toml.get("registries") - .and_then(|registries| registries.get(registry)) - .and_then(|registry| registry.get("token")) - .is_some(), - should_be_set - ); - } else { - assert_eq!( - toml.get("registry") - .and_then(|registry| registry.get("token")) - .is_some(), - should_be_set - ); - } -} - -fn simple_logout_test(registry: &TestRegistry, reg: Option<&str>, flag: &str) { +fn simple_logout_test(registry: &TestRegistry, reg: Option<&str>, flag: &str, note: &str) { let msg = reg.unwrap_or("crates-io"); - check_config_token(reg, true); - let mut cargo = cargo_process(&format!("logout -Z unstable-options {}", flag)); + check_token(Some(registry.token()), reg); + let mut cargo = cargo_process(&format!("logout {}", flag)); if reg.is_none() { cargo.replace_crates_io(registry.index_url()); } cargo - .masquerade_as_nightly_cargo(&["cargo-logout"]) .with_stderr(&format!( "\ -[LOGOUT] token for `{}` has been removed from local storage -", - msg +[LOGOUT] token for `{msg}` has been removed from local storage +[NOTE] This does not revoke the token on the registry server.\n \ +If you need to revoke the token, visit {note} and follow the instructions there. +" )) .run(); - check_config_token(reg, false); + check_token(None, reg); - let mut cargo = cargo_process(&format!("logout -Z unstable-options {}", flag)); + let mut cargo = cargo_process(&format!("logout {}", flag)); if reg.is_none() { cargo.replace_crates_io(registry.index_url()); } cargo - .masquerade_as_nightly_cargo(&["cargo-logout"]) - .with_stderr(&format!( - "\ -[LOGOUT] not currently logged in to `{}` -", - msg - )) + .with_stderr(&format!("[LOGOUT] not currently logged in to `{msg}`")) .run(); - check_config_token(reg, false); + check_token(None, reg); } #[cargo_test] -fn default_registry() { +fn default_registry_unconfigured() { let registry = registry::init(); - simple_logout_test(®istry, None, ""); + simple_logout_test(®istry, None, "", ""); } #[cargo_test] fn other_registry() { let registry = registry::alt_init(); - simple_logout_test(®istry, Some("alternative"), "--registry alternative"); + simple_logout_test( + ®istry, + Some("alternative"), + "--registry alternative", + "the `alternative` website", + ); + // It should not touch crates.io. + check_token(Some("sekrit"), None); +} + +#[cargo_test] +fn default_registry_configured() { + // When registry.default is set, logout should use that one when + // --registry is not used. + let cargo_home = paths::home().join(".cargo"); + cargo_home.mkdir_p(); + cargo_util::paths::write( + &cargo_home.join("config.toml"), + r#" + [registry] + default = "dummy-registry" + + [registries.dummy-registry] + index = "https://127.0.0.1/index" + "#, + ) + .unwrap(); + cargo_util::paths::write( + &cargo_home.join("credentials.toml"), + r#" + [registry] + token = "crates-io-token" + + [registries.dummy-registry] + token = "dummy-token" + "#, + ) + .unwrap(); + check_token(Some("dummy-token"), Some("dummy-registry")); + check_token(Some("crates-io-token"), None); + + cargo_process("logout -Zunstable-options") + .masquerade_as_nightly_cargo(&["cargo-logout"]) + .with_stderr( + "\ +[LOGOUT] token for `dummy-registry` has been removed from local storage +[NOTE] This does not revoke the token on the registry server. + If you need to revoke the token, visit the `dummy-registry` website \ + and follow the instructions there. +", + ) + .run(); + check_token(None, Some("dummy-registry")); + check_token(Some("crates-io-token"), None); + + cargo_process("logout -Zunstable-options") + .masquerade_as_nightly_cargo(&["cargo-logout"]) + .with_stderr("[LOGOUT] not currently logged in to `dummy-registry`") + .run(); } diff --git a/tests/testsuite/lto.rs b/tests/testsuite/lto.rs index 6e7e2e7cb4b..40b4f7ca2f2 100644 --- a/tests/testsuite/lto.rs +++ b/tests/testsuite/lto.rs @@ -627,6 +627,11 @@ fn dylib() { } #[cargo_test] +// This is currently broken on windows-gnu, see https://github.com/rust-lang/rust/issues/109797 +#[cfg_attr( + all(target_os = "windows", target_env = "gnu"), + ignore = "windows-gnu not working" +)] fn test_profile() { Package::new("bar", "0.0.1") .file("src/lib.rs", "pub fn foo() -> i32 { 123 } ") diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs index 66914e4dfd8..a1e293acd81 100644 --- a/tests/testsuite/main.rs +++ b/tests/testsuite/main.rs @@ -42,6 +42,7 @@ mod cross_publish; mod custom_target; mod death; mod dep_info; +mod direct_minimal_versions; mod directory; mod doc; mod docscrape; diff --git a/tests/testsuite/messages.rs b/tests/testsuite/messages.rs index 0ccff152201..2c534d8f071 100644 --- a/tests/testsuite/messages.rs +++ b/tests/testsuite/messages.rs @@ -136,7 +136,7 @@ fn deduplicate_errors() { .with_stderr(&format!( "\ [COMPILING] foo v0.0.1 [..] -{}error: could not compile `foo` due to previous error +{}error: could not compile `foo` (lib) due to previous error ", rustc_message )) diff --git a/tests/testsuite/metadata.rs b/tests/testsuite/metadata.rs index 888f4737217..547916e7a77 100644 --- a/tests/testsuite/metadata.rs +++ b/tests/testsuite/metadata.rs @@ -1797,7 +1797,7 @@ fn cargo_metadata_with_invalid_authors_field() { r#"[ERROR] failed to parse manifest at `[..]` Caused by: - invalid type: string "", expected a sequence + invalid type: string "", expected a vector of strings or workspace in `package.authors`"#, ) .run(); @@ -1847,7 +1847,7 @@ fn cargo_metadata_with_invalid_publish_field() { r#"[ERROR] failed to parse manifest at `[..]` Caused by: - invalid type: string "foo", expected a boolean or vector of strings + invalid type: string "foo", expected a boolean, a vector of strings, or workspace in `package.publish`"#, ) .run(); diff --git a/tests/testsuite/new.rs b/tests/testsuite/new.rs index 1564991c247..b9ddcf2d7c9 100644 --- a/tests/testsuite/new.rs +++ b/tests/testsuite/new.rs @@ -100,6 +100,23 @@ fn simple_git() { cargo_process("build").cwd(&paths::root().join("foo")).run(); } +#[cargo_test(requires_hg)] +fn simple_hg() { + cargo_process("new --lib foo --edition 2015 --vcs hg").run(); + + assert!(paths::root().is_dir()); + assert!(paths::root().join("foo/Cargo.toml").is_file()); + assert!(paths::root().join("foo/src/lib.rs").is_file()); + assert!(paths::root().join("foo/.hg").is_dir()); + assert!(paths::root().join("foo/.hgignore").is_file()); + + let fp = paths::root().join("foo/.hgignore"); + let contents = fs::read_to_string(&fp).unwrap(); + assert_eq!(contents, "^target$\n^Cargo.lock$\n",); + + cargo_process("build").cwd(&paths::root().join("foo")).run(); +} + #[cargo_test] fn no_argument() { cargo_process("new") diff --git a/tests/testsuite/offline.rs b/tests/testsuite/offline.rs index cd4bc10d2b5..fe54fc59df8 100644 --- a/tests/testsuite/offline.rs +++ b/tests/testsuite/offline.rs @@ -1,6 +1,9 @@ //! Tests for --offline flag. -use cargo_test_support::{basic_manifest, git, main_file, path2url, project, registry::Package}; +use cargo_test_support::{ + basic_manifest, git, main_file, path2url, project, + registry::{Package, RegistryBuilder}, +}; use std::fs; #[cargo_test] @@ -331,7 +334,6 @@ Caused by: .run(); } -#[cargo_test] fn update_offline_not_cached() { let p = project() .file( @@ -362,6 +364,17 @@ retry without the offline flag.", .run(); } +#[cargo_test] +fn update_offline_not_cached_sparse() { + let _registry = RegistryBuilder::new().http_index().build(); + update_offline_not_cached() +} + +#[cargo_test] +fn update_offline_not_cached_git() { + update_offline_not_cached() +} + #[cargo_test] fn cargo_compile_offline_with_cached_git_dep() { let git_project = git::new("dep1", |project| { @@ -644,7 +657,7 @@ fn main(){ .with_status(0) .with_stderr( "\ -[UPDATING] present_dep v1.2.9 -> v1.2.3 +[DOWNGRADING] present_dep v1.2.9 -> v1.2.3 ", ) .run(); diff --git a/tests/testsuite/patch.rs b/tests/testsuite/patch.rs index dd8b84a9b13..681c02416bb 100644 --- a/tests/testsuite/patch.rs +++ b/tests/testsuite/patch.rs @@ -2541,3 +2541,105 @@ foo v0.1.0 [..] )) .run(); } + +// From https://github.com/rust-lang/cargo/issues/7463 +#[cargo_test] +fn patch_eq_conflict_panic() { + Package::new("bar", "0.1.0").publish(); + Package::new("bar", "0.1.1").publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + bar = "=0.1.0" + + [dev-dependencies] + bar = "=0.1.1" + + [patch.crates-io] + bar = {path="bar"} + "#, + ) + .file("src/lib.rs", "") + .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.1")) + .file("bar/src/lib.rs", "") + .build(); + + p.cargo("generate-lockfile") + .with_status(101) + .with_stderr( + r#"[UPDATING] `dummy-registry` index +[ERROR] failed to select a version for `bar`. + ... required by package `foo v0.1.0 ([..])` +versions that meet the requirements `=0.1.1` are: 0.1.1 + +all possible versions conflict with previously selected packages. + + previously selected package `bar v0.1.0` + ... which satisfies dependency `bar = "=0.1.0"` of package `foo v0.1.0 ([..])` + +failed to select a version for `bar` which could resolve this conflict +"#, + ) + .run(); +} + +// From https://github.com/rust-lang/cargo/issues/11336 +#[cargo_test] +fn mismatched_version2() { + Package::new("qux", "0.1.0-beta.1").publish(); + Package::new("qux", "0.1.0-beta.2").publish(); + Package::new("bar", "0.1.0") + .dep("qux", "=0.1.0-beta.1") + .publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + bar = "0.1.0" + qux = "0.1.0-beta.2" + + [patch.crates-io] + qux = { path = "qux" } + "#, + ) + .file("src/lib.rs", "") + .file( + "qux/Cargo.toml", + r#" + [package] + name = "qux" + version = "0.1.0-beta.1" + "#, + ) + .file("qux/src/lib.rs", "") + .build(); + + p.cargo("generate-lockfile") + .with_status(101) + .with_stderr( + r#"[UPDATING] `dummy-registry` index +[ERROR] failed to select a version for `qux`. + ... required by package `bar v0.1.0` + ... which satisfies dependency `bar = "^0.1.0"` of package `foo v0.1.0 ([..])` +versions that meet the requirements `=0.1.0-beta.1` are: 0.1.0-beta.1 + +all possible versions conflict with previously selected packages. + + previously selected package `qux v0.1.0-beta.2` + ... which satisfies dependency `qux = "^0.1.0-beta.2"` of package `foo v0.1.0 ([..])` + +failed to select a version for `qux` which could resolve this conflict"#, + ) + .run(); +} diff --git a/tests/testsuite/publish.rs b/tests/testsuite/publish.rs index e3f86905d4c..00a79fe7362 100644 --- a/tests/testsuite/publish.rs +++ b/tests/testsuite/publish.rs @@ -114,7 +114,10 @@ See [..] [PACKAGING] foo v0.0.1 ([CWD]) [PACKAGED] [..] files, [..] ([..] compressed) [UPLOADING] foo v0.0.1 ([CWD]) -[UPDATING] [..] +[UPLOADED] foo v0.0.1 to registry `crates-io` +note: Waiting for `foo v0.0.1` to be available at registry `crates-io`. +You may press ctrl-c to skip waiting; the crate should be available shortly. +[PUBLISHED] foo v0.0.1 at registry `crates-io` ", ) .run(); @@ -155,7 +158,10 @@ See [..] [PACKAGING] foo v0.0.1 ([CWD]) [PACKAGED] [..] files, [..] ([..] compressed) [UPLOADING] foo v0.0.1 ([CWD]) -[UPDATING] `dummy-registry` index +[UPLOADED] foo v0.0.1 to registry `dummy-registry` +note: Waiting for `foo v0.0.1` to be available at registry `dummy-registry`. +You may press ctrl-c to skip waiting; the crate should be available shortly. +[PUBLISHED] foo v0.0.1 at registry `dummy-registry` ", ) .run(); @@ -195,7 +201,10 @@ See [..] [PACKAGING] foo v0.0.1 ([CWD]) [PACKAGED] [..] files, [..] ([..] compressed) [UPLOADING] foo v0.0.1 ([CWD]) -[UPDATING] `dummy-registry` index +[UPLOADED] foo v0.0.1 to registry `dummy-registry` +note: Waiting for `foo v0.0.1` to be available at registry `dummy-registry`. +You may press ctrl-c to skip waiting; the crate should be available shortly. +[PUBLISHED] foo v0.0.1 at registry `dummy-registry` ", ) .run(); @@ -246,7 +255,10 @@ See [..] [PACKAGING] foo v0.0.1 ([CWD]) [PACKAGED] [..] files, [..] ([..] compressed) [UPLOADING] foo v0.0.1 ([CWD]) -[UPDATING] [..] +[UPLOADED] foo v0.0.1 to registry `crates-io` +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] foo v0.0.1 [..] ", ) .run(); @@ -288,7 +300,10 @@ fn simple_with_index() { [..] [..] [UPLOADING] foo v0.0.1 ([CWD]) -[UPDATING] [..] +[UPLOADED] foo v0.0.1 to registry `[ROOT]/registry` +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] foo v0.0.1 [..] ", ) .run(); @@ -486,7 +501,10 @@ fn publish_clean() { [..] [..] [UPLOADING] foo v0.0.1 ([CWD]) -[UPDATING] [..] +[UPLOADED] foo v0.0.1 to registry `crates-io` +note: Waiting [..] +You may press ctrl-c to skip waiting; the crate should be available shortly. +[PUBLISHED] foo v0.0.1 [..] ", ) .run(); @@ -532,7 +550,10 @@ fn publish_in_sub_repo() { [..] [..] [UPLOADING] foo v0.0.1 ([CWD]) -[UPDATING] [..] +[UPLOADED] foo v0.0.1 to registry `crates-io` +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] foo v0.0.1 [..] ", ) .run(); @@ -578,7 +599,10 @@ fn publish_when_ignored() { [..] [..] [UPLOADING] foo v0.0.1 ([CWD]) -[UPDATING] [..] +[UPLOADED] foo v0.0.1 to registry `crates-io` +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] foo v0.0.1 [..] ", ) .run(); @@ -623,7 +647,10 @@ fn ignore_when_crate_ignored() { [..] [..] [UPLOADING] foo v0.0.1 ([CWD]) -[UPDATING] [..] +[UPLOADED] foo v0.0.1 to registry `crates-io` +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] foo v0.0.1 [..] ", ) .run(); @@ -809,7 +836,10 @@ fn publish_allowed_registry() { [..] [..] [UPLOADING] foo v0.0.1 ([CWD]) -[UPDATING] `alternative` index +[UPLOADED] foo v0.0.1 to registry `alternative` +note: Waiting for `foo v0.0.1` to be available at registry `alternative`. +You may press ctrl-c [..] +[PUBLISHED] foo v0.0.1 at registry `alternative` ", ) .run(); @@ -867,7 +897,10 @@ fn publish_implicitly_to_only_allowed_registry() { [..] [..] [UPLOADING] foo v0.0.1 ([CWD]) -[UPDATING] `alternative` index +[UPLOADED] foo v0.0.1 to registry `alternative` +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] foo v0.0.1 [..] ", ) .run(); @@ -993,7 +1026,10 @@ The registry `alternative` is not listed in the `package.publish` value in Cargo [..] [..] [UPLOADING] foo v0.0.1 ([CWD]) -[UPDATING] crates.io index +[UPLOADED] foo v0.0.1 to registry `crates-io` +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] foo v0.0.1 [..] ", ) .run(); @@ -1041,7 +1077,10 @@ fn publish_with_select_features() { [..] [..] [UPLOADING] foo v0.0.1 ([CWD]) -[UPDATING] crates.io index +[UPLOADED] foo v0.0.1 to registry `crates-io` +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] foo v0.0.1 [..] ", ) .run(); @@ -1089,7 +1128,10 @@ fn publish_with_all_features() { [..] [..] [UPLOADING] foo v0.0.1 ([CWD]) -[UPDATING] crates.io index +[UPLOADED] foo v0.0.1 to registry `crates-io` +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] foo v0.0.1 [..] ", ) .run(); @@ -1191,7 +1233,10 @@ fn publish_with_patch() { [..] [..] [UPLOADING] foo v0.0.1 ([CWD]) -[UPDATING] crates.io index +[UPLOADED] foo v0.0.1 to registry `crates-io` +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] foo v0.0.1 [..] ", ) .run(); @@ -1387,7 +1432,10 @@ fn publish_git_with_version() { [..] [..] [UPLOADING] foo v0.1.0 ([CWD]) -[UPDATING] crates.io index +[UPLOADED] foo v0.1.0 to registry `crates-io` +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] foo v0.1.0 [..] ", ) .run(); @@ -1506,7 +1554,10 @@ fn publish_dev_dep_no_version() { [PACKAGING] foo v0.1.0 [..] [PACKAGED] [..] files, [..] ([..] compressed) [UPLOADING] foo v0.1.0 [..] -[UPDATING] crates.io index +[UPLOADED] foo v0.1.0 [..] +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] foo v0.1.0 [..] ", ) .run(); @@ -1602,7 +1653,10 @@ fn credentials_ambiguous_filename() { [..] [..] [UPLOADING] foo v0.0.1 [..] -[UPDATING] crates.io index +[UPLOADED] foo v0.0.1 [..] +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] foo v0.0.1 [..] ", ) .run(); @@ -2009,7 +2063,10 @@ See [..] [PACKAGING] li v0.0.1 ([CWD]/li) [PACKAGED] [..] files, [..] ([..] compressed) [UPLOADING] li v0.0.1 ([CWD]/li) -[UPDATING] crates.io index +[UPLOADED] li v0.0.1 to registry `crates-io` +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] li v0.0.1 [..] ", ) .run(); @@ -2108,7 +2165,10 @@ See [..] [PACKAGING] li v0.0.1 ([CWD]/li) [PACKAGED] [..] files, [..] ([..] compressed) [UPLOADING] li v0.0.1 ([CWD]/li) -[UPDATING] crates.io index +[UPLOADED] li v0.0.1 to registry `crates-io` +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] li v0.0.1 [..] ", ) .run(); @@ -2200,7 +2260,10 @@ See [..] [PACKAGING] li v0.0.1 ([CWD]/li) [PACKAGED] [..] files, [..] ([..] compressed) [UPLOADING] li v0.0.1 ([CWD]/li) -[UPDATING] crates.io index +[UPLOADED] li v0.0.1 to registry `crates-io` +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] li v0.0.1 [..] ", ) .run(); @@ -2387,7 +2450,10 @@ fn http_api_not_noop() { [..] [..] [UPLOADING] foo v0.0.1 ([CWD]) -[UPDATING] [..] +[UPLOADED] foo v0.0.1 to registry `crates-io` +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] foo v0.0.1 [..] ", ) .run(); @@ -2461,8 +2527,10 @@ See [..] [PACKAGING] delay v0.0.1 ([CWD]) [PACKAGED] [..] files, [..] ([..] compressed) [UPLOADING] delay v0.0.1 ([CWD]) -[UPDATING] crates.io index -[WAITING] on `delay` to propagate to crates.io index (ctrl-c to wait asynchronously) +[UPLOADED] delay v0.0.1 to registry `crates-io` +note: Waiting for `delay v0.0.1` to be available at registry `crates-io`. +You may press ctrl-c to skip waiting; the crate should be available shortly. +[PUBLISHED] delay v0.0.1 at registry `crates-io` ", ) .run(); @@ -2498,6 +2566,8 @@ fn wait_for_first_publish_underscore() { // Counter for number of tries before the package is "published" let arc: Arc> = Arc::new(Mutex::new(0)); let arc2 = arc.clone(); + let misses = Arc::new(Mutex::new(Vec::new())); + let misses2 = misses.clone(); // Registry returns an invalid response. let registry = registry::RegistryBuilder::new() @@ -2512,6 +2582,14 @@ fn wait_for_first_publish_underscore() { server.index(req) } }) + .not_found_handler(move |req, _| { + misses.lock().unwrap().push(req.url.to_string()); + Response { + body: b"not found".to_vec(), + code: 404, + headers: vec![], + } + }) .build(); let p = project() @@ -2541,8 +2619,10 @@ See [..] [PACKAGING] delay_with_underscore v0.0.1 ([CWD]) [PACKAGED] [..] files, [..] ([..] compressed) [UPLOADING] delay_with_underscore v0.0.1 ([CWD]) -[UPDATING] crates.io index -[WAITING] on `delay_with_underscore` to propagate to crates.io index (ctrl-c to wait asynchronously) +[UPLOADED] delay_with_underscore v0.0.1 to registry `crates-io` +note: Waiting for `delay_with_underscore v0.0.1` to be available at registry `crates-io`. +You may press ctrl-c to skip waiting; the crate should be available shortly. +[PUBLISHED] delay_with_underscore v0.0.1 at registry `crates-io` ", ) .run(); @@ -2551,6 +2631,13 @@ See [..] let lock = arc2.lock().unwrap(); assert_eq!(*lock, 2); drop(lock); + { + let misses = misses2.lock().unwrap(); + assert!( + misses.len() == 1, + "should only have 1 not found URL; instead found {misses:?}" + ); + } let p = project() .file( @@ -2631,8 +2718,10 @@ See [..] [PACKAGING] delay v0.0.2 ([CWD]) [PACKAGED] [..] files, [..] ([..] compressed) [UPLOADING] delay v0.0.2 ([CWD]) -[UPDATING] crates.io index -[WAITING] on `delay` to propagate to crates.io index (ctrl-c to wait asynchronously) +[UPLOADED] delay v0.0.2 to registry `crates-io` +note: Waiting for `delay v0.0.2` to be available at registry `crates-io`. +You may press ctrl-c to skip waiting; the crate should be available shortly. +[PUBLISHED] delay v0.0.2 at registry `crates-io` ", ) .run(); @@ -2657,7 +2746,7 @@ See [..] .file("src/main.rs", "fn main() {}") .build(); - p.cargo("build").with_status(0).run(); + p.cargo("check").with_status(0).run(); } #[cargo_test] @@ -2702,3 +2791,161 @@ See [..] ) .run(); } + +#[cargo_test] +fn timeout_waiting_for_publish() { + // Publish doesn't happen within the timeout window. + let registry = registry::RegistryBuilder::new() + .http_api() + .delayed_index_update(20) + .build(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "delay" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + "#, + ) + .file("src/lib.rs", "") + .file( + ".cargo/config.toml", + r#" + [publish] + timeout = 2 + "#, + ) + .build(); + + p.cargo("publish --no-verify -Zpublish-timeout") + .replace_crates_io(registry.index_url()) + .masquerade_as_nightly_cargo(&["publish-timeout"]) + .with_status(0) + .with_stderr( + "\ +[UPDATING] crates.io index +[WARNING] manifest has no documentation, [..] +See [..] +[PACKAGING] delay v0.0.1 ([CWD]) +[PACKAGED] [..] files, [..] ([..] compressed) +[UPLOADING] delay v0.0.1 ([CWD]) +[UPLOADED] delay v0.0.1 to registry `crates-io` +note: Waiting for `delay v0.0.1` to be available at registry `crates-io`. +You may press ctrl-c to skip waiting; the crate should be available shortly. +warning: timed out waiting for `delay v0.0.1` to be available in registry `crates-io` +note: The registry may have a backlog that is delaying making the crate available. The crate should be available soon. +", + ) + .run(); +} + +#[cargo_test] +fn wait_for_git_publish() { + // Slow publish to an index with a git index. + let registry = registry::RegistryBuilder::new() + .http_api() + .delayed_index_update(5) + .build(); + + // Publish an earlier version + Package::new("delay", "0.0.1") + .file("src/lib.rs", "") + .publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "delay" + version = "0.0.2" + authors = [] + license = "MIT" + description = "foo" + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("publish --no-verify") + .replace_crates_io(registry.index_url()) + .with_status(0) + .with_stderr( + "\ +[UPDATING] crates.io index +[WARNING] manifest has no documentation, [..] +See [..] +[PACKAGING] delay v0.0.2 ([CWD]) +[PACKAGED] [..] files, [..] ([..] compressed) +[UPLOADING] delay v0.0.2 ([CWD]) +[UPLOADED] delay v0.0.2 to registry `crates-io` +note: Waiting for `delay v0.0.2` to be available at registry `crates-io`. +You may press ctrl-c to skip waiting; the crate should be available shortly. +[PUBLISHED] delay v0.0.2 at registry `crates-io` +", + ) + .run(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + [dependencies] + delay = "0.0.2" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("check").with_status(0).run(); +} + +#[cargo_test] +fn invalid_token() { + // Checks publish behavior with an invalid token. + let registry = RegistryBuilder::new().http_api().http_index().build(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + documentation = "foo" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("publish --no-verify") + .replace_crates_io(registry.index_url()) + .env("CARGO_REGISTRY_TOKEN", "\x16") + .with_stderr( + "\ +[UPDATING] crates.io index +[PACKAGING] foo v0.0.1 ([ROOT]/foo) +[PACKAGED] 4 files, [..] +[UPLOADING] foo v0.0.1 ([ROOT]/foo) +error: failed to publish to registry at http://127.0.0.1:[..]/ + +Caused by: + token contains invalid characters. + Only printable ISO-8859-1 characters are allowed as it is sent in a HTTPS header. +", + ) + .with_status(101) + .run(); +} diff --git a/tests/testsuite/registry.rs b/tests/testsuite/registry.rs index 626436ae73f..05ec9b15860 100644 --- a/tests/testsuite/registry.rs +++ b/tests/testsuite/registry.rs @@ -4,13 +4,15 @@ use cargo::core::SourceId; use cargo_test_support::cargo_process; use cargo_test_support::paths::{self, CargoPathExt}; use cargo_test_support::registry::{ - self, registry_path, Dependency, Package, RegistryBuilder, TestRegistry, + self, registry_path, Dependency, Package, RegistryBuilder, Response, TestRegistry, }; use cargo_test_support::{basic_manifest, project}; use cargo_test_support::{git, install::cargo_home, t}; use cargo_util::paths::remove_dir_all; +use std::fmt::Write; use std::fs::{self, File}; use std::path::Path; +use std::sync::Arc; use std::sync::Mutex; fn setup_http() -> TestRegistry { @@ -1926,6 +1928,7 @@ Caused by: #[cargo_test] fn disallow_network_git() { + let _server = RegistryBuilder::new().build(); let p = project() .file( "Cargo.toml", @@ -1952,7 +1955,10 @@ Caused by: failed to load source for dependency `foo` Caused by: - Unable to update registry [..] + Unable to update registry `crates-io` + +Caused by: + failed to update replaced source registry `crates-io` Caused by: attempting to make an HTTP request, but --frozen was specified @@ -2700,7 +2706,7 @@ Caused by: } #[cargo_test] -fn sparse_retry() { +fn sparse_retry_single() { let fail_count = Mutex::new(0); let _registry = RegistryBuilder::new() .http_index() @@ -2737,10 +2743,10 @@ fn sparse_retry() { .with_stderr( "\ [UPDATING] `dummy-registry` index -warning: spurious network error (2 tries remaining): failed to get successful HTTP response from `[..]`, got 500 +warning: spurious network error (3 tries remaining): failed to get successful HTTP response from `[..]` (127.0.0.1), got 500 body: internal server error -warning: spurious network error (1 tries remaining): failed to get successful HTTP response from `[..]`, got 500 +warning: spurious network error (2 tries remaining): failed to get successful HTTP response from `[..]` (127.0.0.1), got 500 body: internal server error [DOWNLOADING] crates ... @@ -2753,6 +2759,226 @@ internal server error .run(); } +#[cargo_test] +fn sparse_retry_multiple() { + // Tests retry behavior of downloading lots of packages with various + // failure rates accessing the sparse index. + + // The index is the number of retries, the value is the number of packages + // that retry that number of times. Thus 50 packages succeed on first try, + // 25 on second, etc. + const RETRIES: &[u32] = &[50, 25, 12, 6]; + + let pkgs: Vec<_> = RETRIES + .iter() + .enumerate() + .flat_map(|(retries, num)| { + (0..*num) + .into_iter() + .map(move |n| (retries as u32, format!("{}-{n}-{retries}", rand_prefix()))) + }) + .collect(); + + let mut builder = RegistryBuilder::new().http_index(); + let fail_counts: Arc>> = Arc::new(Mutex::new(vec![0; pkgs.len()])); + let mut cargo_toml = r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + "# + .to_string(); + // The expected stderr output. + let mut expected = "\ +[UPDATING] `dummy-registry` index +[DOWNLOADING] crates ... +" + .to_string(); + for (n, (retries, name)) in pkgs.iter().enumerate() { + let count_clone = fail_counts.clone(); + let retries = *retries; + let ab = &name[..2]; + let cd = &name[2..4]; + builder = builder.add_responder(format!("/index/{ab}/{cd}/{name}"), move |req, server| { + let mut fail_counts = count_clone.lock().unwrap(); + if fail_counts[n] < retries { + fail_counts[n] += 1; + server.internal_server_error(req) + } else { + server.index(req) + } + }); + write!(&mut cargo_toml, "{name} = \"1.0.0\"\n").unwrap(); + for retry in 0..retries { + let remain = 3 - retry; + write!( + &mut expected, + "warning: spurious network error ({remain} tries remaining): \ + failed to get successful HTTP response from \ + `http://127.0.0.1:[..]/{ab}/{cd}/{name}` (127.0.0.1), got 500\n\ + body:\n\ + internal server error\n" + ) + .unwrap(); + } + write!( + &mut expected, + "[DOWNLOADED] {name} v1.0.0 (registry `dummy-registry`)\n" + ) + .unwrap(); + } + let _server = builder.build(); + for (_, name) in &pkgs { + Package::new(name, "1.0.0").publish(); + } + let p = project() + .file("Cargo.toml", &cargo_toml) + .file("src/lib.rs", "") + .build(); + p.cargo("fetch").with_stderr_unordered(expected).run(); +} + +#[cargo_test] +fn dl_retry_single() { + // Tests retry behavior of downloading a package. + // This tests a single package which exercises the code path that causes + // it to block. + let fail_count = Mutex::new(0); + let _server = RegistryBuilder::new() + .http_index() + .add_responder("/dl/bar/1.0.0/download", move |req, server| { + let mut fail_count = fail_count.lock().unwrap(); + if *fail_count < 2 { + *fail_count += 1; + server.internal_server_error(req) + } else { + server.dl(req) + } + }) + .build(); + Package::new("bar", "1.0.0").publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + bar = "1.0" + "#, + ) + .file("src/lib.rs", "") + .build(); + p.cargo("fetch") + .with_stderr("\ +[UPDATING] `dummy-registry` index +[DOWNLOADING] crates ... +warning: spurious network error (3 tries remaining): \ + failed to get successful HTTP response from `http://127.0.0.1:[..]/dl/bar/1.0.0/download` (127.0.0.1), got 500 +body: +internal server error +warning: spurious network error (2 tries remaining): \ + failed to get successful HTTP response from `http://127.0.0.1:[..]/dl/bar/1.0.0/download` (127.0.0.1), got 500 +body: +internal server error +[DOWNLOADED] bar v1.0.0 (registry `dummy-registry`) +").run(); +} + +/// Creates a random prefix to randomly spread out the package names +/// to somewhat evenly distribute the different failures at different +/// points. +fn rand_prefix() -> String { + use rand::Rng; + const CHARS: &[u8] = b"abcdefghijklmnopqrstuvwxyz"; + let mut rng = rand::thread_rng(); + (0..5) + .map(|_| CHARS[rng.gen_range(0..CHARS.len())] as char) + .collect() +} + +#[cargo_test] +fn dl_retry_multiple() { + // Tests retry behavior of downloading lots of packages with various + // failure rates. + + // The index is the number of retries, the value is the number of packages + // that retry that number of times. Thus 50 packages succeed on first try, + // 25 on second, etc. + const RETRIES: &[u32] = &[50, 25, 12, 6]; + + let pkgs: Vec<_> = RETRIES + .iter() + .enumerate() + .flat_map(|(retries, num)| { + (0..*num) + .into_iter() + .map(move |n| (retries as u32, format!("{}-{n}-{retries}", rand_prefix()))) + }) + .collect(); + + let mut builder = RegistryBuilder::new().http_index(); + let fail_counts: Arc>> = Arc::new(Mutex::new(vec![0; pkgs.len()])); + let mut cargo_toml = r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + "# + .to_string(); + // The expected stderr output. + let mut expected = "\ +[UPDATING] `dummy-registry` index +[DOWNLOADING] crates ... +" + .to_string(); + for (n, (retries, name)) in pkgs.iter().enumerate() { + let count_clone = fail_counts.clone(); + let retries = *retries; + builder = + builder.add_responder(format!("/dl/{name}/1.0.0/download"), move |req, server| { + let mut fail_counts = count_clone.lock().unwrap(); + if fail_counts[n] < retries { + fail_counts[n] += 1; + server.internal_server_error(req) + } else { + server.dl(req) + } + }); + write!(&mut cargo_toml, "{name} = \"1.0.0\"\n").unwrap(); + for retry in 0..retries { + let remain = 3 - retry; + write!( + &mut expected, + "warning: spurious network error ({remain} tries remaining): \ + failed to get successful HTTP response from \ + `http://127.0.0.1:[..]/dl/{name}/1.0.0/download` (127.0.0.1), got 500\n\ + body:\n\ + internal server error\n" + ) + .unwrap(); + } + write!( + &mut expected, + "[DOWNLOADED] {name} v1.0.0 (registry `dummy-registry`)\n" + ) + .unwrap(); + } + let _server = builder.build(); + for (_, name) in &pkgs { + Package::new(name, "1.0.0").publish(); + } + let p = project() + .file("Cargo.toml", &cargo_toml) + .file("src/lib.rs", "") + .build(); + p.cargo("fetch").with_stderr_unordered(expected).run(); +} + #[cargo_test] fn deleted_entry() { // Checks the behavior when a package is removed from the index. @@ -2911,3 +3137,270 @@ fn corrupted_ok_overwritten() { p.cargo("fetch").with_stderr("").run(); assert_eq!(fs::read_to_string(&ok).unwrap(), "ok"); } + +#[cargo_test] +fn not_found_permutations() { + // Test for querying permutations for a missing dependency. + let misses = Arc::new(Mutex::new(Vec::new())); + let misses2 = misses.clone(); + let _registry = RegistryBuilder::new() + .http_index() + .not_found_handler(move |req, _server| { + let mut misses = misses2.lock().unwrap(); + misses.push(req.url.path().to_string()); + Response { + code: 404, + headers: vec![], + body: b"not found".to_vec(), + } + }) + .build(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies] + a-b-c = "1.0" + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("check") + .with_status(101) + .with_stderr( + "\ +[UPDATING] `dummy-registry` index +error: no matching package named `a-b-c` found +location searched: registry `crates-io` +required by package `foo v0.0.1 ([ROOT]/foo)` +", + ) + .run(); + let mut misses = misses.lock().unwrap(); + misses.sort(); + assert_eq!( + &*misses, + &[ + "/index/a-/b-/a-b-c", + "/index/a-/b_/a-b_c", + "/index/a_/b-/a_b-c", + "/index/a_/b_/a_b_c" + ] + ); +} + +#[cargo_test] +fn default_auth_error() { + // Check for the error message for an authentication error when default is set. + let crates_io = RegistryBuilder::new().http_api().build(); + let _alternative = RegistryBuilder::new().http_api().alternative().build(); + + paths::home().join(".cargo/credentials.toml").rm_rf(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + license = "MIT" + description = "foo" + "#, + ) + .file("src/lib.rs", "") + .build(); + + // Test output before setting the default. + p.cargo("publish --no-verify") + .replace_crates_io(crates_io.index_url()) + .with_stderr( + "\ +[UPDATING] crates.io index +error: no token found, please run `cargo login` +or use environment variable CARGO_REGISTRY_TOKEN +", + ) + .with_status(101) + .run(); + + p.cargo("publish --no-verify --registry alternative") + .replace_crates_io(crates_io.index_url()) + .with_stderr( + "\ +[UPDATING] `alternative` index +error: no token found for `alternative`, please run `cargo login --registry alternative` +or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN +", + ) + .with_status(101) + .run(); + + // Test the output with the default. + cargo_util::paths::append( + &cargo_home().join("config"), + br#" + [registry] + default = "alternative" + "#, + ) + .unwrap(); + + p.cargo("publish --no-verify") + .replace_crates_io(crates_io.index_url()) + .with_stderr( + "\ +[UPDATING] `alternative` index +error: no token found for `alternative`, please run `cargo login --registry alternative` +or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN +", + ) + .with_status(101) + .run(); + + p.cargo("publish --no-verify --registry crates-io") + .replace_crates_io(crates_io.index_url()) + .with_stderr( + "\ +[UPDATING] crates.io index +error: no token found, please run `cargo login --registry crates-io` +or use environment variable CARGO_REGISTRY_TOKEN +", + ) + .with_status(101) + .run(); +} + +const SAMPLE_HEADERS: &[&str] = &[ + "x-amz-cf-pop: SFO53-P2", + "x-amz-cf-id: vEc3osJrCAXVaciNnF4Vev-hZFgnYwmNZtxMKRJ5bF6h9FTOtbTMnA==", + "x-cache: Hit from cloudfront", + "server: AmazonS3", + "x-amz-version-id: pvsJYY_JGsWiSETZvLJKb7DeEW5wWq1W", + "x-amz-server-side-encryption: AES256", + "content-type: text/plain", + "via: 1.1 bcbc5b46216015493e082cfbcf77ef10.cloudfront.net (CloudFront)", +]; + +#[cargo_test] +fn debug_header_message_index() { + // The error message should include some headers for debugging purposes. + let _server = RegistryBuilder::new() + .http_index() + .add_responder("/index/3/b/bar", |_, _| Response { + code: 503, + headers: SAMPLE_HEADERS.iter().map(|s| s.to_string()).collect(), + body: b"Please slow down".to_vec(), + }) + .build(); + Package::new("bar", "1.0.0").publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + bar = "1.0" + "#, + ) + .file("src/lib.rs", "") + .build(); + p.cargo("fetch").with_status(101).with_stderr("\ +[UPDATING] `dummy-registry` index +warning: spurious network error (3 tries remaining): \ + failed to get successful HTTP response from `http://127.0.0.1:[..]/index/3/b/bar` (127.0.0.1), got 503 +body: +Please slow down +warning: spurious network error (2 tries remaining): \ + failed to get successful HTTP response from `http://127.0.0.1:[..]/index/3/b/bar` (127.0.0.1), got 503 +body: +Please slow down +warning: spurious network error (1 tries remaining): \ + failed to get successful HTTP response from `http://127.0.0.1:[..]/index/3/b/bar` (127.0.0.1), got 503 +body: +Please slow down +error: failed to get `bar` as a dependency of package `foo v0.1.0 ([ROOT]/foo)` + +Caused by: + failed to query replaced source registry `crates-io` + +Caused by: + download of 3/b/bar failed + +Caused by: + failed to get successful HTTP response from `http://127.0.0.1:[..]/index/3/b/bar` (127.0.0.1), got 503 + debug headers: + x-amz-cf-pop: SFO53-P2 + x-amz-cf-id: vEc3osJrCAXVaciNnF4Vev-hZFgnYwmNZtxMKRJ5bF6h9FTOtbTMnA== + x-cache: Hit from cloudfront + body: + Please slow down +").run(); +} + +#[cargo_test] +fn debug_header_message_dl() { + // Same as debug_header_message_index, but for the dl endpoint which goes + // through a completely different code path. + let _server = RegistryBuilder::new() + .http_index() + .add_responder("/dl/bar/1.0.0/download", |_, _| Response { + code: 503, + headers: SAMPLE_HEADERS.iter().map(|s| s.to_string()).collect(), + body: b"Please slow down".to_vec(), + }) + .build(); + Package::new("bar", "1.0.0").publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + bar = "1.0" + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("fetch").with_status(101).with_stderr("\ +[UPDATING] `dummy-registry` index +[DOWNLOADING] crates ... +warning: spurious network error (3 tries remaining): \ + failed to get successful HTTP response from `http://127.0.0.1:[..]/dl/bar/1.0.0/download` (127.0.0.1), got 503 +body: +Please slow down +warning: spurious network error (2 tries remaining): \ + failed to get successful HTTP response from `http://127.0.0.1:[..]/dl/bar/1.0.0/download` (127.0.0.1), got 503 +body: +Please slow down +warning: spurious network error (1 tries remaining): \ + failed to get successful HTTP response from `http://127.0.0.1:[..]/dl/bar/1.0.0/download` (127.0.0.1), got 503 +body: +Please slow down +error: failed to download from `http://127.0.0.1:[..]/dl/bar/1.0.0/download` + +Caused by: + failed to get successful HTTP response from `http://127.0.0.1:[..]/dl/bar/1.0.0/download` (127.0.0.1), got 503 + debug headers: + x-amz-cf-pop: SFO53-P2 + x-amz-cf-id: vEc3osJrCAXVaciNnF4Vev-hZFgnYwmNZtxMKRJ5bF6h9FTOtbTMnA== + x-cache: Hit from cloudfront + body: + Please slow down +").run(); +} diff --git a/tests/testsuite/registry_auth.rs b/tests/testsuite/registry_auth.rs index 3989dc9ec2f..7779e285a89 100644 --- a/tests/testsuite/registry_auth.rs +++ b/tests/testsuite/registry_auth.rs @@ -1,4 +1,4 @@ -//! Tests for normal registry dependencies. +//! Tests for registry authentication. use cargo_test_support::registry::{Package, RegistryBuilder}; use cargo_test_support::{project, Execs, Project}; @@ -57,7 +57,7 @@ fn requires_nightly() { error: failed to download from `[..]/dl/bar/0.0.1/download` Caused by: - failed to get successful HTTP response from `[..]`, got 401 + failed to get successful HTTP response from `[..]` (127.0.0.1), got 401 body: Unauthorized message from server. "#, @@ -415,7 +415,7 @@ fn incorrect_token_git() { [ERROR] failed to download from `http://[..]/dl/bar/0.0.1/download` Caused by: - failed to get successful HTTP response from `http://[..]/dl/bar/0.0.1/download`, got 401 + failed to get successful HTTP response from `http://[..]/dl/bar/0.0.1/download` (127.0.0.1), got 401 body: Unauthorized message from server.", ) diff --git a/tests/testsuite/run.rs b/tests/testsuite/run.rs index 7b3f2970569..aa210d6aec0 100644 --- a/tests/testsuite/run.rs +++ b/tests/testsuite/run.rs @@ -592,7 +592,7 @@ fn run_bins() { "\ error: unexpected argument '--bins' found - note: argument '--bin' exists", + tip: a similar argument exists: '--bin'", ) .run(); } diff --git a/tests/testsuite/rustdoc_extern_html.rs b/tests/testsuite/rustdoc_extern_html.rs index 440da56f599..b18358d1c90 100644 --- a/tests/testsuite/rustdoc_extern_html.rs +++ b/tests/testsuite/rustdoc_extern_html.rs @@ -404,8 +404,8 @@ fn alt_sparse_registry() { "#, ) .build(); - p.cargo("doc -v --no-deps -Zrustdoc-map -Zsparse-registry") - .masquerade_as_nightly_cargo(&["rustdoc-map", "sparse-registry"]) + p.cargo("doc -v --no-deps -Zrustdoc-map") + .masquerade_as_nightly_cargo(&["rustdoc-map"]) .with_stderr_contains( "[RUNNING] `rustdoc [..]--crate-name foo \ [..]bar=https://example.com/bar/1.0.0/[..]grimm=https://docs.rs/grimm/1.0.0/[..]", diff --git a/tests/testsuite/source_replacement.rs b/tests/testsuite/source_replacement.rs index ef7cc555c04..24f2ca3e3e3 100644 --- a/tests/testsuite/source_replacement.rs +++ b/tests/testsuite/source_replacement.rs @@ -220,7 +220,10 @@ See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for [FINISHED] dev [..] [PACKAGED] [..] [UPLOADING] foo v0.0.1 ([..]) -[UPDATING] crates.io index +[UPLOADED] foo v0.0.1 to registry `crates-io` +note: Waiting for `foo v0.0.1` to be available at registry `crates-io`. +You may press ctrl-c to skip waiting; the crate should be available shortly. +[PUBLISHED] foo v0.0.1 at registry `crates-io` ", ) .run(); diff --git a/tests/testsuite/ssh.rs b/tests/testsuite/ssh.rs index d812be275d9..d1701d32d6b 100644 --- a/tests/testsuite/ssh.rs +++ b/tests/testsuite/ssh.rs @@ -6,6 +6,7 @@ //! NOTE: The container tests almost certainly won't work on Windows. use cargo_test_support::containers::{Container, ContainerHandle, MkFile}; +use cargo_test_support::git::cargo_uses_gitoxide; use cargo_test_support::{paths, process, project, Project}; use std::fs; use std::io::Write; @@ -415,7 +416,11 @@ fn invalid_github_key() { .build(); p.cargo("fetch") .with_status(101) - .with_stderr_contains(" error: SSH host key has changed for `github.com`") + .with_stderr_contains(if cargo_uses_gitoxide() { + " git@github.com: Permission denied (publickey)." + } else { + " error: SSH host key has changed for `github.com`" + }) .run(); } @@ -447,16 +452,7 @@ fn bundled_github_works() { ) .file("src/lib.rs", "") .build(); - let err = if cfg!(windows) { - "error authenticating: unable to connect to agent pipe; class=Ssh (23)" - } else { - "error authenticating: failed connecting with agent; class=Ssh (23)" - }; - p.cargo("fetch") - .env("SSH_AUTH_SOCK", &bogus_auth_sock) - .with_status(101) - .with_stderr(&format!( - "\ + let shared_stderr = "\ [UPDATING] git repository `ssh://git@github.com/rust-lang/bitflags.git` error: failed to get `bitflags` as a dependency of package `foo v0.1.0 ([ROOT]/foo)` @@ -472,34 +468,40 @@ Caused by: Caused by: failed to authenticate when downloading repository - * attempted ssh-agent authentication, but no usernames succeeded: `git` + *"; + let expected = if cargo_uses_gitoxide() { + format!( + "{shared_stderr} attempted to find username/password via `credential.helper`, but maybe the found credentials were incorrect if the git CLI succeeds then `net.git-fetch-with-cli` may help here https://doc.rust-lang.org/cargo/reference/config.html#netgit-fetch-with-cli Caused by: - {err} + Credentials provided for \"ssh://git@github.com/rust-lang/bitflags.git\" were not accepted by the remote + +Caused by: + git@github.com: Permission denied (publickey). " - )) - .run(); + ) + } else { + format!( + "{shared_stderr} attempted ssh-agent authentication, but no usernames succeeded: `git` - // Explicit :22 should also work with bundled. - p.change_file( - "Cargo.toml", - r#" - [package] - name = "foo" - version = "0.1.0" + if the git CLI succeeds then `net.git-fetch-with-cli` may help here + https://doc.rust-lang.org/cargo/reference/config.html#netgit-fetch-with-cli - [dependencies] - bitflags = { git = "ssh://git@github.com:22/rust-lang/bitflags.git", tag = "1.3.2" } - "#, - ); +Caused by: + no authentication methods succeeded +" + ) + }; p.cargo("fetch") .env("SSH_AUTH_SOCK", &bogus_auth_sock) .with_status(101) - .with_stderr(&format!( - "\ + .with_stderr(&expected) + .run(); + + let shared_stderr = "\ [UPDATING] git repository `ssh://git@github.com:22/rust-lang/bitflags.git` error: failed to get `bitflags` as a dependency of package `foo v0.1.0 ([ROOT]/foo)` @@ -515,15 +517,51 @@ Caused by: Caused by: failed to authenticate when downloading repository - * attempted ssh-agent authentication, but no usernames succeeded: `git` + *"; + + let expected = if cargo_uses_gitoxide() { + format!( + "{shared_stderr} attempted to find username/password via `credential.helper`, but maybe the found credentials were incorrect if the git CLI succeeds then `net.git-fetch-with-cli` may help here https://doc.rust-lang.org/cargo/reference/config.html#netgit-fetch-with-cli Caused by: - {err} + Credentials provided for \"ssh://git@github.com:22/rust-lang/bitflags.git\" were not accepted by the remote + +Caused by: + git@github.com: Permission denied (publickey). " - )) + ) + } else { + format!( + "{shared_stderr} attempted ssh-agent authentication, but no usernames succeeded: `git` + + if the git CLI succeeds then `net.git-fetch-with-cli` may help here + https://doc.rust-lang.org/cargo/reference/config.html#netgit-fetch-with-cli + +Caused by: + no authentication methods succeeded +" + ) + }; + + // Explicit :22 should also work with bundled. + p.change_file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + bitflags = { git = "ssh://git@github.com:22/rust-lang/bitflags.git", tag = "1.3.2" } + "#, + ); + p.cargo("fetch") + .env("SSH_AUTH_SOCK", &bogus_auth_sock) + .with_status(101) + .with_stderr(&expected) .run(); } diff --git a/tests/testsuite/update.rs b/tests/testsuite/update.rs index a2105ba3efe..057c8fca4a3 100644 --- a/tests/testsuite/update.rs +++ b/tests/testsuite/update.rs @@ -385,7 +385,7 @@ fn update_precise() { .with_stderr( "\ [UPDATING] `[..]` index -[UPDATING] serde v0.2.1 -> v0.2.0 +[DOWNGRADING] serde v0.2.1 -> v0.2.0 ", ) .run(); @@ -492,7 +492,7 @@ fn update_precise_first_run() { .with_stderr( "\ [UPDATING] `[..]` index -[UPDATING] serde v0.2.1 -> v0.2.0 +[DOWNGRADING] serde v0.2.1 -> v0.2.0 ", ) .run(); diff --git a/tests/testsuite/vendor.rs b/tests/testsuite/vendor.rs index 2b179fe2306..21a1c097c0f 100644 --- a/tests/testsuite/vendor.rs +++ b/tests/testsuite/vendor.rs @@ -88,8 +88,7 @@ fn vendor_sample_config_alt_registry() { Package::new("log", "0.3.5").alternative(true).publish(); - p.cargo("vendor --respect-source-config -Z sparse-registry") - .masquerade_as_nightly_cargo(&["sparse-registry"]) + p.cargo("vendor --respect-source-config") .with_stdout(format!( r#"[source."{0}"] registry = "{0}" diff --git a/tests/testsuite/weak_dep_features.rs b/tests/testsuite/weak_dep_features.rs index dfc1e6b794e..ee91114df5f 100644 --- a/tests/testsuite/weak_dep_features.rs +++ b/tests/testsuite/weak_dep_features.rs @@ -561,7 +561,10 @@ fn publish() { [FINISHED] [..] [PACKAGED] [..] [UPLOADING] foo v0.1.0 [..] -[UPDATING] [..] +[UPLOADED] foo v0.1.0 to registry `crates-io` +note: Waiting for `foo v0.1.0` to be available at registry `crates-io`. +You may press ctrl-c to skip waiting; the crate should be available shortly. +[PUBLISHED] foo v0.1.0 at registry `crates-io` ", ) .run(); diff --git a/triagebot.toml b/triagebot.toml index 510c0c77c5b..192859537af 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -40,8 +40,7 @@ warn_non_default_branch = true [autolabel."A-build-execution"] trigger_files = [ "src/cargo/core/compiler/compilation.rs", - "src/cargo/core/compiler/job.rs", - "src/cargo/core/compiler/job_queue.rs", + "src/cargo/core/compiler/job_queue/", "src/cargo/core/compiler/mod.rs", ] @@ -209,7 +208,7 @@ trigger_files = [ ] [autolabel."Command-add"] -trigger_files = ["src/bin/cargo/commands/add.rs", "src/cargo/ops/cargo_add/*"] +trigger_files = ["src/bin/cargo/commands/add.rs", "src/cargo/ops/cargo_add/"] [autolabel."Command-bench"] trigger_files = ["src/bin/cargo/commands/bench.rs"] @@ -300,7 +299,7 @@ trigger_files = ["src/bin/cargo/commands/search.rs"] trigger_files = ["src/bin/cargo/commands/test.rs", "src/cargo/ops/cargo_test.rs"] [autolabel."Command-tree"] -trigger_files = ["src/bin/cargo/commands/tree.rs", "src/cargo/ops/tree/*"] +trigger_files = ["src/bin/cargo/commands/tree.rs", "src/cargo/ops/tree/"] [autolabel."Command-uninstall"] trigger_files = ["src/bin/cargo/commands/uninstall.rs", "src/cargo/ops/cargo_uninstall.rs"]