diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-07 05:48:48 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-07 05:48:48 +0000 |
commit | ef24de24a82fe681581cc130f342363c47c0969a (patch) | |
tree | 0d494f7e1a38b95c92426f58fe6eaa877303a86c /src/tools/cargo | |
parent | Releasing progress-linux version 1.74.1+dfsg1-1~progress7.99u1. (diff) | |
download | rustc-ef24de24a82fe681581cc130f342363c47c0969a.tar.xz rustc-ef24de24a82fe681581cc130f342363c47c0969a.zip |
Merging upstream version 1.75.0+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/tools/cargo')
351 files changed, 8940 insertions, 4496 deletions
diff --git a/src/tools/cargo/.github/renovate.json5 b/src/tools/cargo/.github/renovate.json5 index b633fc245..03e6d8da8 100644 --- a/src/tools/cargo/.github/renovate.json5 +++ b/src/tools/cargo/.github/renovate.json5 @@ -12,29 +12,59 @@ { customType: 'regex', fileMatch: [ - '^Cargo.toml$', + 'Cargo.toml$', ], matchStrings: [ - 'rust-version.*?(?<currentValue>\\d+\\.\\d+(\\.\\d+)?)', + '\bMSRV:1\b.*?(?<currentValue>\\d+\\.\\d+(\\.\\d+)?)', + '(?<currentValue>\\d+\\.\\d+(\\.\\d+)?).*?\bMSRV:1\b', ], - depNameTemplate: 'latest-msrv', + depNameTemplate: 'MSRV:1', // Support 1 version of rustc + packageNameTemplate: 'rust-lang/rust', + datasourceTemplate: 'github-releases', + }, + { + customType: 'regex', + fileMatch: [ + 'Cargo.toml$', + ], + matchStrings: [ + '\bMSRV:3\b.*?(?<currentValue>\\d+\\.\\d+(\\.\\d+)?)', + '(?<currentValue>\\d+\\.\\d+(\\.\\d+)?).*?\bMSRV:3\b', + ], + depNameTemplate: 'MSRV:3', // Support 3 versions of rustc packageNameTemplate: 'rust-lang/rust', datasourceTemplate: 'github-releases', }, ], packageRules: [ { - commitMessageTopic: 'Latest MSRV', + commitMessageTopic: 'MSRV (1 version)', + matchManagers: [ + 'regex', + ], + matchPackageNames: [ + 'MSRV:1', + ], + schedule: [ + '* * * * *', + ], + groupName: 'msrv', + }, + { + commitMessageTopic: 'MSRV (3 versions)', matchManagers: [ 'regex', ], matchPackageNames: [ - 'latest-msrv', + 'MSRV:3', ], "extractVersion": "^(?<version>\\d+\\.\\d+)", // Drop the patch version schedule: [ '* * * * *', ], + minimumReleaseAge: '85 days', // 2 releases back * 6 weeks per release * 7 days per week + 1 + internalChecksFilter: 'strict', + groupName: 'msrv', }, // Goals: // - Rollup safe upgrades to reduce CI runner load diff --git a/src/tools/cargo/.github/workflows/audit.yml b/src/tools/cargo/.github/workflows/audit.yml index 14e35b7b3..d903eb0d7 100644 --- a/src/tools/cargo/.github/workflows/audit.yml +++ b/src/tools/cargo/.github/workflows/audit.yml @@ -21,7 +21,7 @@ jobs: - advisories - bans licenses sources steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: EmbarkStudios/cargo-deny-action@v1 # Prevent sudden announcement of a new advisory from failing ci: continue-on-error: ${{ matrix.checks == 'advisories' }} diff --git a/src/tools/cargo/.github/workflows/contrib.yml b/src/tools/cargo/.github/workflows/contrib.yml index bbd4a7ef7..a4c1cb5d0 100644 --- a/src/tools/cargo/.github/workflows/contrib.yml +++ b/src/tools/cargo/.github/workflows/contrib.yml @@ -4,6 +4,10 @@ on: branches: - master +concurrency: + cancel-in-progress: false + group: "gh-pages" + permissions: contents: read @@ -13,7 +17,7 @@ jobs: contents: write # for Git to git push runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Install mdbook @@ -23,16 +27,26 @@ jobs: echo `pwd`/mdbook >> $GITHUB_PATH - name: Deploy docs run: | + GENERATE_PY="$(pwd)/ci/generate.py" + cd src/doc/contrib mdbook build - git worktree add gh-pages gh-pages + + # Override previous ref to avoid keeping history. + git worktree add --orphan -B gh-pages gh-pages git config user.name "Deploy from CI" git config user.email "" cd gh-pages - # Delete the ref to avoid keeping history. - git update-ref -d refs/heads/gh-pages - rm -rf contrib mv ../book contrib git add contrib + + # Generate HTML for link redirections. + python3 "$GENERATE_PY" + git add *.html + # WARN: The CNAME file is for GitHub to redirect requests to the custom domain. + # Missing this may entail security hazard and domain takeover. + # See <https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/managing-a-custom-domain-for-your-github-pages-site#securing-your-custom-domain> + git add CNAME + git commit -m "Deploy $GITHUB_SHA to gh-pages" - git push --force + git push origin +gh-pages diff --git a/src/tools/cargo/.github/workflows/main.yml b/src/tools/cargo/.github/workflows/main.yml index 44dd76e13..7b8055223 100644 --- a/src/tools/cargo/.github/workflows/main.yml +++ b/src/tools/cargo/.github/workflows/main.yml @@ -20,7 +20,7 @@ jobs: needs: - build_std - clippy - - credential_msrv + - msrv - docs - lockfile - resolver @@ -38,7 +38,7 @@ jobs: needs: - build_std - clippy - - credential_msrv + - msrv - docs - lockfile - resolver @@ -54,7 +54,7 @@ jobs: rustfmt: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: rustup update stable && rustup default stable - run: rustup component add rustfmt - run: cargo fmt --all --check @@ -63,7 +63,7 @@ jobs: clippy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: rustup update stable && rustup default stable - run: rustup component add clippy # Only check cargo lib for now @@ -73,7 +73,7 @@ jobs: stale-label: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: rustup update stable && rustup default stable - run: cargo stale-label @@ -81,7 +81,7 @@ jobs: lockfile: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: rustup update stable && rustup default stable - run: cargo update -p cargo --locked @@ -91,14 +91,14 @@ jobs: BASE_SHA: ${{ github.event.pull_request.base.sha }} HEAD_SHA: ${{ github.event.pull_request.head.sha != '' && github.event.pull_request.head.sha || github.sha }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - run: rustup update stable && rustup default stable - name: Install cargo-semver-checks run: | mkdir installed-bins - curl -Lf https://github.com/obi1kenobi/cargo-semver-checks/releases/download/v0.22.1/cargo-semver-checks-x86_64-unknown-linux-gnu.tar.gz \ + curl -Lf https://github.com/obi1kenobi/cargo-semver-checks/releases/download/v0.24.0/cargo-semver-checks-x86_64-unknown-linux-gnu.tar.gz \ | tar -xz --directory=./installed-bins echo `pwd`/installed-bins >> $GITHUB_PATH - run: ci/validate-version-bump.sh @@ -145,7 +145,7 @@ jobs: other: i686-pc-windows-gnu name: Tests ${{ matrix.name }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Dump Environment run: ci/dump-environment.sh - run: rustup update --no-self-update ${{ matrix.rust }} && rustup default ${{ matrix.rust }} @@ -196,14 +196,14 @@ jobs: resolver: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: rustup update stable && rustup default stable - run: cargo test -p resolver-tests test_gitoxide: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - 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 @@ -215,7 +215,7 @@ jobs: build_std: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: rustup update nightly && rustup default nightly - run: rustup component add rust-src - run: cargo build @@ -225,7 +225,7 @@ jobs: docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: rustup update nightly && rustup default nightly - run: rustup update stable - run: rustup component add rust-docs @@ -249,9 +249,9 @@ jobs: curl -sSLO https://raw.githubusercontent.com/rust-lang/rust/master/src/tools/linkchecker/linkcheck.sh sh linkcheck.sh --all --path ../src/doc cargo - credential_msrv: + msrv: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - run: rustup update 1.70 && rustup default 1.70 - - run: cargo test -p cargo-credential + - uses: actions/checkout@v4 + - uses: taiki-e/install-action@cargo-hack + - run: cargo hack check --all-targets --rust-version --workspace --ignore-private diff --git a/src/tools/cargo/CHANGELOG.md b/src/tools/cargo/CHANGELOG.md index f2c9bd0eb..08be017a1 100644 --- a/src/tools/cargo/CHANGELOG.md +++ b/src/tools/cargo/CHANGELOG.md @@ -1,16 +1,150 @@ # Changelog +## Cargo 1.75 (2023-12-28) +[59596f0f...HEAD](https://github.com/rust-lang/cargo/compare/59596f0f...HEAD) + +### Added + +### Changed + +### Fixed + +- Fixed corruption when cargo was killed while writing to files. + [#12744](https://github.com/rust-lang/cargo/pull/12744) + +### Nightly only + +### Documentation + +- profile: add missing `strip` info. + [#12754](https://github.com/rust-lang/cargo/pull/12754) + +### Internal + +- Updated to `itertools` 0.11.0. + [#12759](https://github.com/rust-lang/cargo/pull/12759) +- Updated to `cargo_metadata` 0.18.0. + [#12758](https://github.com/rust-lang/cargo/pull/12758) +- Disabled the `custom_target::custom_bin_target` test on windows-gnu. + [#12763](https://github.com/rust-lang/cargo/pull/12763) + ## Cargo 1.74 (2023-11-16) -[80eca0e5...HEAD](https://github.com/rust-lang/cargo/compare/80eca0e5...HEAD) +[80eca0e5...rust-1.74.0](https://github.com/rust-lang/cargo/compare/80eca0e5...rust-1.74.0) ### Added +- 🎉 The `[lints]` table has been stabilized, allowing you to configure reporting levels for rustc and other tool lints in `Cargo.toml`. + ([RFC 3389](https://github.com/rust-lang/rfcs/blob/master/text/3389-manifest-lint.md)) + ([docs](https://doc.rust-lang.org/nightly/cargo/reference/manifest.html#the-lints-section)) + [#12584](https://github.com/rust-lang/cargo/pull/12584) + [#12648](https://github.com/rust-lang/cargo/pull/12648) +- 🎉 The unstable features `credential-process` and `registry-auth` have been stabilized. + These features consolidate the way to authenticate with private registries. + ([RFC 2730](https://github.com/rust-lang/rfcs/blob/master/text/2730-cargo-token-from-process.md)) + ([RFC 3139](https://github.com/rust-lang/rfcs/blob/master/text/3139-cargo-alternative-registry-auth.md)) + ([docs](https://doc.rust-lang.org/nightly/cargo/reference/registry-authentication.html)) + [#12590](https://github.com/rust-lang/cargo/pull/12590) + [#12622](https://github.com/rust-lang/cargo/pull/12622) + [#12623](https://github.com/rust-lang/cargo/pull/12623) + [#12626](https://github.com/rust-lang/cargo/pull/12626) + [#12641](https://github.com/rust-lang/cargo/pull/12641) + [#12644](https://github.com/rust-lang/cargo/pull/12644) + [#12649](https://github.com/rust-lang/cargo/pull/12649) + [#12671](https://github.com/rust-lang/cargo/pull/12671) + [#12709](https://github.com/rust-lang/cargo/pull/12709) + Notable changes: + - Introducing a new protocol for both external and built-in providers to store and retrieve credentials for registry authentication. + - Adding the `auth-required` field in the registry index's `config.json`, enabling authenticated sparse index, crate downloads, and search API. + - For using alternative registries with authentication, a credential provider must be configured to avoid unknowingly storing unencrypted credentials on disk. + - These settings can be configured in `[registry]` and `[registries]` tables. +- 🎉 `--keep-going` flag has been stabilized and is now available in each build command + (except `bench` and `test`, which have `--no-fail-fast` instead). + ([docs](https://doc.rust-lang.org/cargo/commands/cargo-build.html#option-cargo-build---keep-going)) + [#12568](https://github.com/rust-lang/cargo/pull/12568) +- Added `--dry-run` flag and summary line at the end for `cargo clean`. + [#12638](https://github.com/rust-lang/cargo/pull/12638) +- Added a short alias `-n` for cli option `--dry-run`. + [#12660](https://github.com/rust-lang/cargo/pull/12660) +- Added support for `target.'cfg(..)'.linker`. + [#12535](https://github.com/rust-lang/cargo/pull/12535) +- Allowed incomplete versions when they are unambiguous for flags like `--package`. + [#12591](https://github.com/rust-lang/cargo/pull/12591) + [#12614](https://github.com/rust-lang/cargo/pull/12614) + ### Changed +- ❗️ Changed how arrays in configuration are merged. + The order was unspecified and now follows how other configuration types work for consistency. + [summary](https://blog.rust-lang.org/inside-rust/2023/08/24/cargo-config-merging.html) + [#12515](https://github.com/rust-lang/cargo/pull/12515) +- ❗️ cargo-clean: error out if `--doc` is mixed with `-p`. + [#12637](https://github.com/rust-lang/cargo/pull/12637) +- cargo-update: silently deprecate `--aggressive` in favor of the new `--recursive`. + [#12544](https://github.com/rust-lang/cargo/pull/12544) +- cargo-update: `-p/--package` can be used as a positional argument. + [#12545](https://github.com/rust-lang/cargo/pull/12545) + [#12586](https://github.com/rust-lang/cargo/pull/12586) +- cargo-install: suggest `--git` when the package name looks like a URL. + [#12575](https://github.com/rust-lang/cargo/pull/12575) +- cargo-add: summarize the feature list when it's too long. + [#12662](https://github.com/rust-lang/cargo/pull/12662) + [#12702](https://github.com/rust-lang/cargo/pull/12702) +- Shell completion for `--target` uses rustup but falls back to rustc. + [#12606](https://github.com/rust-lang/cargo/pull/12606) +- Help users know possible `--target` values. + [#12607](https://github.com/rust-lang/cargo/pull/12607) +- Enhanced "registry index not found" error message. + [#12732](https://github.com/rust-lang/cargo/pull/12732) +- Enhanced CLI help message of `--explain`. + [#12592](https://github.com/rust-lang/cargo/pull/12592) +- Enhanced deserialization errors of untagged enums with `serde-untagged`. + [#12574](https://github.com/rust-lang/cargo/pull/12574) + [#12581](https://github.com/rust-lang/cargo/pull/12581) +- Enhanced the error when mismatching prerelease version candidates. + [#12659](https://github.com/rust-lang/cargo/pull/12659) +- Enhanced the suggestion on ambiguous Package ID spec. + [#12685](https://github.com/rust-lang/cargo/pull/12685) +- Enhanced TOML parse errors to show the context. + [#12556](https://github.com/rust-lang/cargo/pull/12556) +- Enhanced filesystem error by adding wrappers around `std::fs::metadata`. + [#12636](https://github.com/rust-lang/cargo/pull/12636) +- Enhanced resolver version mismatch warning. + [#12573](https://github.com/rust-lang/cargo/pull/12573) +- Use clap to suggest alternative argument for unsupported arguments. + [#12529](https://github.com/rust-lang/cargo/pull/12529) + [#12693](https://github.com/rust-lang/cargo/pull/12693) + [#12723](https://github.com/rust-lang/cargo/pull/12723) +- Removed redundant information from cargo new/init `--help` output. + [#12594](https://github.com/rust-lang/cargo/pull/12594) +- Console output and styling tweaks. + [#12578](https://github.com/rust-lang/cargo/pull/12578) + [#12655](https://github.com/rust-lang/cargo/pull/12655) + [#12593](https://github.com/rust-lang/cargo/pull/12593) + ### Fixed +- Use full target spec for `cargo rustc --print --target`. + [#12743](https://github.com/rust-lang/cargo/pull/12743) +- Copy PDBs also for EFI targets. + [#12688](https://github.com/rust-lang/cargo/pull/12688) +- Fixed resolver behavior being independent of package order. + [#12602](https://github.com/rust-lang/cargo/pull/12602) +- Fixed unnecessary clean up of `profile.release.package."*"` for `cargo remove`. + [#12624](https://github.com/rust-lang/cargo/pull/12624) + ### Nightly only +- `-Zasymmetric-token`: Created dedicated unstable flag for asymmetric-token support. + [#12551](https://github.com/rust-lang/cargo/pull/12551) +- `-Zasymmetric-token`: Improved logout message for asymmetric tokens. + [#12587](https://github.com/rust-lang/cargo/pull/12587) +- `-Zmsrv-policy`: **Very** preliminary MSRV resolver support. + [#12560](https://github.com/rust-lang/cargo/pull/12560) +- `-Zscript`: Hack in code fence support. + [#12681](https://github.com/rust-lang/cargo/pull/12681) +- `-Zbindeps`: Support dependencies from registries. + [#12421](https://github.com/rust-lang/cargo/pull/12421) + ### Documentation - ❗ Policy change: Checking `Cargo.lock` into version control is now the default choice, @@ -19,6 +153,83 @@ [Lockfile docs](https://doc.rust-lang.org/nightly/cargo/guide/cargo-toml-vs-cargo-lock.html), [CI docs](https://doc.rust-lang.org/nightly/cargo/guide/continuous-integration.html), [#12382](https://github.com/rust-lang/cargo/pull/12382) + [#12630](https://github.com/rust-lang/cargo/pull/12630) +- SemVer: Update documentation about removing optional dependencies. + [#12687](https://github.com/rust-lang/cargo/pull/12687) +- Contrib: Add process for security responses. + [#12487](https://github.com/rust-lang/cargo/pull/12487) +- cargo-publish: warn about upload timeout. + [#12733](https://github.com/rust-lang/cargo/pull/12733) +- mdbook: use *AND* search when having multiple terms. + [#12548](https://github.com/rust-lang/cargo/pull/12548) +- Established publish best practices + [#12745](https://github.com/rust-lang/cargo/pull/12745) +- Clarify caret requirements. + [#12679](https://github.com/rust-lang/cargo/pull/12679) +- Clarify how `version` works for `git` dependencies. + [#12270](https://github.com/rust-lang/cargo/pull/12270) +- Clarify and differentiate defaults for split-debuginfo. + [#12680](https://github.com/rust-lang/cargo/pull/12680) +- Added missing `strip` entries in `dev` and `release` profiles. + [#12748](https://github.com/rust-lang/cargo/pull/12748) + +### Internal + +- Updated to `curl-sys` 0.4.66, which corresponds to curl 8.3.0. + [#12718](https://github.com/rust-lang/cargo/pull/12718) +- Updated to `gitoxide` 0.54.1. + [#12731](https://github.com/rust-lang/cargo/pull/12731) +- Updated to `git2` 0.18.0, which corresponds to libgit2 1.7.1. + [#12580](https://github.com/rust-lang/cargo/pull/12580) +- Updated to `cargo_metadata` 0.17.0. + [#12758](https://github.com/rust-lang/cargo/pull/12610) +- Updated target-arch-aware crates to support mips r6 targets + [#12720](https://github.com/rust-lang/cargo/pull/12720) +- publish.py: Remove obsolete `sleep()` calls. + [#12686](https://github.com/rust-lang/cargo/pull/12686) +- Define `{{command}}` for use in src/doc/man/includes + [#12570](https://github.com/rust-lang/cargo/pull/12570) +- Set tracing target `network` for networking messages. + [#12582](https://github.com/rust-lang/cargo/pull/12582) +- cargo-test-support: Add `with_stdout_unordered`. + [#12635](https://github.com/rust-lang/cargo/pull/12635) +- dep: Switch from `termcolor` to `anstream`. + [#12751](https://github.com/rust-lang/cargo/pull/12751) +- Put `Source` trait under `cargo::sources`. + [#12527](https://github.com/rust-lang/cargo/pull/12527) +- SourceId: merge `name` and `alt_registry_key` into one enum. + [#12675](https://github.com/rust-lang/cargo/pull/12675) +- TomlManifest: fail when package_root is not a directory. + [#12722](https://github.com/rust-lang/cargo/pull/12722) +- util: enhanced doc of `network::retry` doc. + [#12583](https://github.com/rust-lang/cargo/pull/12583) +- refactor: Pull out cargo-add MSRV code for reuse + [#12553](https://github.com/rust-lang/cargo/pull/12553) +- refactor(install): Move value parsing to clap + [#12547](https://github.com/rust-lang/cargo/pull/12547) +- Fixed spurious errors with networking tests. + [#12726](https://github.com/rust-lang/cargo/pull/12726) +- Use a more compact relative-time format for `CARGO_LOG` internal logging. + [#12542](https://github.com/rust-lang/cargo/pull/12542) +- Use newer std API for cleaner code. + [#12559](https://github.com/rust-lang/cargo/pull/12559) + [#12604](https://github.com/rust-lang/cargo/pull/12604) + [#12615](https://github.com/rust-lang/cargo/pull/12615) + [#12631](https://github.com/rust-lang/cargo/pull/12631) +- Buffer console status messages. + [#12727](https://github.com/rust-lang/cargo/pull/12727) +- Use enum to describe index summaries to provide a richer information when summaries are not available for resolution. + [#12643](https://github.com/rust-lang/cargo/pull/12643) +- Use shortest path for resolving the path from the given dependency up to the root. + [#12678](https://github.com/rust-lang/cargo/pull/12678) +- Read/write the encoded `cargo update --precise` in the same place + [#12629](https://github.com/rust-lang/cargo/pull/12629) +- Set MSRV for internal packages. + [#12381](https://github.com/rust-lang/cargo/pull/12381) +- ci: Update Renovate schema + [#12741](https://github.com/rust-lang/cargo/pull/12741) +- ci: Ignore patch version in MSRV + [#12716](https://github.com/rust-lang/cargo/pull/12716) ## Cargo 1.73 (2023-10-05) [45782b6b...rust-1.73.0](https://github.com/rust-lang/cargo/compare/45782b6b...rust-1.73.0) @@ -32,9 +243,13 @@ ### Changed -- Cargo now bails out when using `cargo::` in custom build scripts. This is +- ❗️ Cargo now bails out when using `cargo::` in custom build scripts. This is a preparation for an upcoming change in build script invocations. [#12332](https://github.com/rust-lang/cargo/pull/12332) +- ❗️ `cargo login` no longer accept any token after the `--` syntax. + Arguments after `--` are now reserved in the preparation of the new credential provider feature. + This introduces a regression that overlooks the `cargo login -- <token>` support in preivous versions. + [#12499](https://github.com/rust-lang/cargo/pull/12499) - Make Cargo `--help` easier to browse. [#11905](https://github.com/rust-lang/cargo/pull/11905) - Prompt the use of `--nocapture` flag if `cargo test` process is terminated via a signal. diff --git a/src/tools/cargo/CONTRIBUTING.md b/src/tools/cargo/CONTRIBUTING.md index 88ffbd3d0..6046d5a35 100644 --- a/src/tools/cargo/CONTRIBUTING.md +++ b/src/tools/cargo/CONTRIBUTING.md @@ -8,14 +8,12 @@ Contributing documentation has moved to the **[Cargo Contributor Guide]**. We encourage people to discuss their design before hacking on code. Typically, you [file an issue] or start a thread on the [internals forum] before submitting -a pull request. Please read [the process] of how features and bugs are managed -in Cargo. +a pull request. -**NOTICE: Due to limited review capacity, the Cargo team is not accepting new -features or major changes at this time. Please consult with the team before -opening a new PR. Only issues that have been explicitly marked as accepted -will be reviewed.** +Please read [the process] of how features and bugs are managed in Cargo. +**Only issues that have been explicitly marked as [accepted] will be reviewed.** [internals forum]: https://internals.rust-lang.org/c/tools-and-infrastructure/cargo [file an issue]: https://github.com/rust-lang/cargo/issues [the process]: https://doc.crates.io/contrib/process/index.html +[accepted]: https://github.com/rust-lang/cargo/issues?q=is%3Aissue+is%3Aopen+label%3AS-accepted diff --git a/src/tools/cargo/Cargo.lock b/src/tools/cargo/Cargo.lock index cc0cb9a88..a2d339b0c 100644 --- a/src/tools/cargo/Cargo.lock +++ b/src/tools/cargo/Cargo.lock @@ -25,9 +25,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d7b3983a025adeb201ef26a5564ebd1641ea9851f6282aee4940f745a3c07c" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" dependencies = [ "anstyle", "anstyle-parse", @@ -97,9 +97,9 @@ checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] name = "base64" -version = "0.21.3" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" [[package]] name = "base64ct" @@ -141,9 +141,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" [[package]] name = "bitmaps" @@ -190,12 +190,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] name = "bytes" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -208,12 +202,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc" [[package]] -name = "byteyarn" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7534301c0ea17abb4db06d75efc7b4b0fa360fce8e175a4330d721c71c942ff" - -[[package]] name = "camino" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -234,7 +222,7 @@ dependencies = [ [[package]] name = "cargo" -version = "0.75.0" +version = "0.76.0" dependencies = [ "anstream", "anstyle", @@ -245,7 +233,7 @@ dependencies = [ "cargo-credential-libsecret", "cargo-credential-macos-keychain", "cargo-credential-wincred", - "cargo-platform 0.1.5", + "cargo-platform 0.1.6", "cargo-test-macro", "cargo-test-support", "cargo-util", @@ -259,7 +247,7 @@ dependencies = [ "git2", "git2-curl", "gix", - "gix-features", + "gix-features 0.35.0", "glob", "hex", "hmac", @@ -269,7 +257,7 @@ dependencies = [ "ignore", "im-rc", "indexmap", - "itertools", + "itertools 0.11.0", "jobserver", "lazycell", "libc", @@ -293,7 +281,8 @@ dependencies = [ "sha1", "shell-escape", "snapbox", - "syn 2.0.29", + "supports-hyperlinks", + "syn 2.0.38", "tar", "tempfile", "time", @@ -311,7 +300,7 @@ dependencies = [ [[package]] name = "cargo-credential" -version = "0.4.0" +version = "0.4.1" dependencies = [ "anyhow", "libc", @@ -325,7 +314,7 @@ dependencies = [ [[package]] name = "cargo-credential-1password" -version = "0.4.0" +version = "0.4.2" dependencies = [ "cargo-credential", "serde", @@ -334,7 +323,7 @@ dependencies = [ [[package]] name = "cargo-credential-libsecret" -version = "0.3.2" +version = "0.4.1" dependencies = [ "anyhow", "cargo-credential", @@ -343,7 +332,7 @@ dependencies = [ [[package]] name = "cargo-credential-macos-keychain" -version = "0.3.1" +version = "0.4.1" dependencies = [ "cargo-credential", "security-framework", @@ -351,7 +340,7 @@ dependencies = [ [[package]] name = "cargo-credential-wincred" -version = "0.3.1" +version = "0.4.1" dependencies = [ "cargo-credential", "windows-sys", @@ -368,7 +357,7 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.5" +version = "0.1.6" dependencies = [ "serde", ] @@ -391,7 +380,7 @@ dependencies = [ "flate2", "git2", "glob", - "itertools", + "itertools 0.11.0", "pasetors", "serde", "serde_json", @@ -405,7 +394,7 @@ dependencies = [ [[package]] name = "cargo-util" -version = "0.2.7" +version = "0.2.8" dependencies = [ "anyhow", "core-foundation", @@ -425,9 +414,9 @@ dependencies = [ [[package]] name = "cargo_metadata" -version = "0.17.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7daec1a2a2129eeba1644b220b4647ec537b0b5d4bfd6876fcc5a540056b592" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" dependencies = [ "camino", "cargo-platform 0.1.2", @@ -487,18 +476,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.6" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" +checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.4.6" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" +checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663" dependencies = [ "anstream", "anstyle", @@ -509,9 +498,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "clru" @@ -521,18 +510,18 @@ checksum = "b8191fa7302e03607ff0e237d4246cc043ff5b3cb9409d995172ba3bea16b807" [[package]] name = "color-print" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2a5e6504ed8648554968650feecea00557a3476bc040d0ffc33080e66b646d0" +checksum = "7a858372ff14bab9b1b30ea504f2a4bc534582aee3e42ba2d41d2a7baba63d5d" dependencies = [ "color-print-proc-macro", ] [[package]] name = "color-print-proc-macro" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51beaa537d73d2d1ff34ee70bc095f170420ab2ec5d687ecd3ec2b0d092514b" +checksum = "57e37866456a721d0a404439a1adae37a31be4e0055590d053dfe6981e05003f" dependencies = [ "nom", "proc-macro2", @@ -588,7 +577,7 @@ dependencies = [ [[package]] name = "crates-io" -version = "0.39.0" +version = "0.39.1" dependencies = [ "curl", "percent-encoding", @@ -619,7 +608,7 @@ dependencies = [ "clap", "criterion-plot", "is-terminal", - "itertools", + "itertools 0.10.5", "num-traits", "once_cell", "oorandom", @@ -640,7 +629,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", - "itertools", + "itertools 0.10.5", ] [[package]] @@ -935,15 +924,15 @@ checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.3.5", "windows-sys", ] [[package]] name = "flate2" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ "crc32fast", "libz-sys", @@ -1006,11 +995,11 @@ dependencies = [ [[package]] name = "git2" -version = "0.18.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12ef350ba88a33b4d524b1d1c79096c9ade5ef8c59395df0e60d1e1889414c0e" +checksum = "fbf97ba92db08df386e10c8ede66a2a0369bd277090afd8710e19e38de9ec0cd" dependencies = [ - "bitflags 2.3.3", + "bitflags 2.4.0", "libc", "libgit2-sys", "log", @@ -1033,9 +1022,9 @@ dependencies = [ [[package]] name = "gix" -version = "0.54.1" +version = "0.55.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad6d32e74454459690d57d18ea4ebec1629936e6b130b51d12cb4a81630ac953" +checksum = "002667cd1ebb789313d0d0afe3d23b2821cf3b0e91605095f0e6d8751f0ceeea" dependencies = [ "gix-actor", "gix-attributes", @@ -1045,7 +1034,7 @@ dependencies = [ "gix-date", "gix-diff", "gix-discover", - "gix-features", + "gix-features 0.36.0", "gix-filter", "gix-fs", "gix-glob", @@ -1087,9 +1076,9 @@ dependencies = [ [[package]] name = "gix-actor" -version = "0.27.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c60e982c5290897122d4e2622447f014a2dadd5a18cb73d50bb91b31645e27" +checksum = "948a5f9e43559d16faf583694f1c742eb401ce24ce8e6f2238caedea7486433c" dependencies = [ "bstr", "btoi", @@ -1101,16 +1090,16 @@ dependencies = [ [[package]] name = "gix-attributes" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2451665e70709ba4753b623ef97511ee98c4a73816b2c5b5df25678d607ed820" +checksum = "dca120f0c6562d2d7cae467f2466e576d9f7f189beec2af2e026145107c729e2" dependencies = [ "bstr", - "byteyarn", "gix-glob", "gix-path", "gix-quote", "gix-trace", + "kstring", "smallvec", "thiserror", "unicode-bom", @@ -1136,22 +1125,22 @@ dependencies = [ [[package]] name = "gix-command" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f28f654184b5f725c5737c7e4f466cbd8f0102ac352d5257eeab19647ee4256" +checksum = "3c576cfbf577f72c097b5f88aedea502cd62952bdc1fb3adcab4531d5525a4c7" dependencies = [ "bstr", ] [[package]] name = "gix-commitgraph" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e75a975ee22cf0a002bfe9b5d5cb3d2a88e263a8a178cd7509133cff10f4df8a" +checksum = "7e8bc78b1a6328fa6d8b3a53b6c73997af37fd6bfc1d6c49f149e63bda5cbb36" dependencies = [ "bstr", "gix-chunk", - "gix-features", + "gix-features 0.36.0", "gix-hash", "memmap2", "thiserror", @@ -1159,13 +1148,13 @@ dependencies = [ [[package]] name = "gix-config" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c171514b40487d3f677ae37efc0f45ac980e3169f23c27eb30a70b47fdf88ab5" +checksum = "5cae98c6b4c66c09379bc35274b172587d6b0ac369a416c39128ad8c6454f9bb" dependencies = [ "bstr", "gix-config-value", - "gix-features", + "gix-features 0.36.0", "gix-glob", "gix-path", "gix-ref", @@ -1184,7 +1173,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea7505b97f4d8e7933e29735a568ba2f86d8de466669d9f0e8321384f9972f47" dependencies = [ - "bitflags 2.3.3", + "bitflags 2.4.0", "bstr", "gix-path", "libc", @@ -1193,9 +1182,9 @@ dependencies = [ [[package]] name = "gix-credentials" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46900b884cc5af6a6c141ee741607c0c651a4e1d33614b8d888a1ba81cc0bc8a" +checksum = "1c5c5d74069b842a1861e581027ac6b7ad9ff66f5911c89b9f45484d7ebda6a4" dependencies = [ "bstr", "gix-command", @@ -1221,9 +1210,9 @@ dependencies = [ [[package]] name = "gix-diff" -version = "0.36.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "788ddb152c388206e81f36bcbb574e7ed7827c27d8fa62227b34edc333d8928c" +checksum = "931394f69fb8c9ed6afc0aae3487bd869e936339bcc13ed8884472af072e0554" dependencies = [ "gix-hash", "gix-object", @@ -1232,9 +1221,9 @@ dependencies = [ [[package]] name = "gix-discover" -version = "0.25.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69507643d75a0ea9a402fcf73ced517d2b95cc95385904ac09d03e0b952fde33" +checksum = "a45d5cf0321178883e38705ab2b098f625d609a7d4c391b33ac952eff2c490f2" dependencies = [ "bstr", "dunce", @@ -1251,15 +1240,26 @@ version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b9ff423ae4983f762659040d13dd7a5defbd54b6a04ac3cc7347741cec828cd" dependencies = [ + "crossbeam-channel", + "gix-hash", + "gix-trace", + "libc", + "parking_lot", +] + +[[package]] +name = "gix-features" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51f4365ba17c4f218d7fd9ec102b8d2d3cb0ca200a835e81151ace7778aec827" +dependencies = [ "bytes", "crc32fast", - "crossbeam-channel", "flate2", "gix-hash", "gix-trace", "libc", "once_cell", - "parking_lot", "prodash", "sha1_smol", "thiserror", @@ -1268,9 +1268,9 @@ dependencies = [ [[package]] name = "gix-filter" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1be40d28cd41445bb6cd52c4d847d915900e5466f7433eaee6a9e0a3d1d88b08" +checksum = "92f674d3fdb6b1987b04521ec9a5b7be8650671f2c4bbd17c3c81e2a364242ff" dependencies = [ "bstr", "encoding_rs", @@ -1288,30 +1288,30 @@ dependencies = [ [[package]] name = "gix-fs" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09815faba62fe9b32d918b75a554686c98e43f7d48c43a80df58eb718e5c6635" +checksum = "8cd171c0cae97cd0dc57e7b4601cb1ebf596450e263ef3c02be9107272c877bd" dependencies = [ - "gix-features", + "gix-features 0.36.0", ] [[package]] name = "gix-glob" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d76e85f11251dcf751d2c5e918a14f562db5be6f727fd24775245653e9b19d" +checksum = "8fac08925dbc14d414bd02eb45ffb4cecd912d1fce3883f867bd0103c192d3e4" dependencies = [ - "bitflags 2.3.3", + "bitflags 2.4.0", "bstr", - "gix-features", + "gix-features 0.36.0", "gix-path", ] [[package]] name = "gix-hash" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ccf425543779cddaa4a7c62aba3fa9d90ea135b160be0a72dd93c063121ad4a" +checksum = "1884c7b41ea0875217c1be9ce91322f90bde433e91d374d0e1276073a51ccc60" dependencies = [ "faster-hex", "thiserror", @@ -1330,9 +1330,9 @@ dependencies = [ [[package]] name = "gix-ignore" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048f443a1f6b02da4205c34d2e287e3fd45d75e8e2f06cfb216630ea9bff5e3" +checksum = "1e73c07763a8005ae02cb5cf83040729cea9bb70c7cef68ec6c24159904c499a" dependencies = [ "bstr", "gix-glob", @@ -1342,16 +1342,16 @@ dependencies = [ [[package]] name = "gix-index" -version = "0.25.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f54d63a9d13c13088f41f5a3accbec284e492ac8f4f707fcc307c139622e17b7" +checksum = "c83a4fcc121b2f2e109088f677f89f85e7a8ebf39e8e6659c0ae54d4283b1650" dependencies = [ - "bitflags 2.3.3", + "bitflags 2.4.0", "bstr", "btoi", "filetime", "gix-bitmap", - "gix-features", + "gix-features 0.36.0", "gix-fs", "gix-hash", "gix-lock", @@ -1365,9 +1365,9 @@ dependencies = [ [[package]] name = "gix-lock" -version = "10.0.0" +version = "11.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47fc96fa8b6b6d33555021907c81eb3b27635daecf6e630630bdad44f8feaa95" +checksum = "f4feb1dcd304fe384ddc22edba9dd56a42b0800032de6537728cea2f033a4f37" dependencies = [ "gix-tempfile", "gix-utils", @@ -1382,16 +1382,16 @@ checksum = "9d8acb5ee668d55f0f2d19a320a3f9ef67a6999ad483e11135abcc2464ed18b6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.38", ] [[package]] name = "gix-negotiate" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f1697bf9911c6d1b8d709b9e6ef718cb5ea5821a1b7991520125a8134448004" +checksum = "2a5cdcf491ecc9ce39dcc227216c540355fe0024ae7c38e94557752ca5ebb67f" dependencies = [ - "bitflags 2.3.3", + "bitflags 2.4.0", "gix-commitgraph", "gix-date", "gix-hash", @@ -1403,15 +1403,15 @@ dependencies = [ [[package]] name = "gix-object" -version = "0.37.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7e19616c67967374137bae83e950e9b518a9ea8a605069bd6716ada357fd6f" +checksum = "740f2a44267f58770a1cb3a3d01d14e67b089c7136c48d4bddbb3cfd2bf86a51" dependencies = [ "bstr", "btoi", "gix-actor", "gix-date", - "gix-features", + "gix-features 0.36.0", "gix-hash", "gix-validate", "itoa 1.0.6", @@ -1422,13 +1422,13 @@ dependencies = [ [[package]] name = "gix-odb" -version = "0.53.0" +version = "0.54.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d6a392c6ba3a2f133cdc63120e9bc7aec81eef763db372c817de31febfe64bf" +checksum = "8630b56cb80d8fa684d383dad006a66401ee8314e12fbf0e566ddad8c115143b" dependencies = [ "arc-swap", "gix-date", - "gix-features", + "gix-features 0.36.0", "gix-hash", "gix-object", "gix-pack", @@ -1441,13 +1441,13 @@ dependencies = [ [[package]] name = "gix-pack" -version = "0.43.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7536203a45b31e1bc5694bbf90ba8da1b736c77040dd6a520db369f371eb1ab3" +checksum = "1431ba2e30deff1405920693d54ab231c88d7c240dd6ccc936ee223d8f8697c3" dependencies = [ "clru", "gix-chunk", - "gix-features", + "gix-features 0.36.0", "gix-hash", "gix-hashtable", "gix-object", @@ -1461,9 +1461,9 @@ dependencies = [ [[package]] name = "gix-packetline" -version = "0.16.6" +version = "0.16.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6df0b75361353e7c0a6d72d49617a37379a7a22cba4569ae33a7720a4c8755a" +checksum = "8a8384b1e964151aff0d5632dd9b191059d07dff358b96bd940f1b452600d7ab" dependencies = [ "bstr", "faster-hex", @@ -1496,11 +1496,11 @@ dependencies = [ [[package]] name = "gix-pathspec" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e26c9b47c51be73f98d38c84494bd5fb99334c5d6fda14ef5d036d50a9e5fd" +checksum = "e9cc7194fdcf43b4a1ccfa13ffae1d79f83beb4becff7761d88dd99faeafe625" dependencies = [ - "bitflags 2.3.3", + "bitflags 2.4.0", "bstr", "gix-attributes", "gix-config-value", @@ -1524,15 +1524,15 @@ dependencies = [ [[package]] name = "gix-protocol" -version = "0.40.0" +version = "0.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7b700dc20cc9be8a5130a1fd7e10c34117ffa7068431c8c24d963f0a2e0c9b" +checksum = "391e3feabdfa5f90dad6673ce59e3291ac28901b2ff248d86c5a7fbde0391e0e" dependencies = [ "bstr", "btoi", "gix-credentials", "gix-date", - "gix-features", + "gix-features 0.36.0", "gix-hash", "gix-transport", "maybe-async", @@ -1553,13 +1553,13 @@ dependencies = [ [[package]] name = "gix-ref" -version = "0.37.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e6b749660b613641769edc1954132eb8071a13c32224891686091bef078de4" +checksum = "0ec2f6d07ac88d2fb8007ee3fa3e801856fb9d82e7366ec0ca332eb2c9d74a52" dependencies = [ "gix-actor", "gix-date", - "gix-features", + "gix-features 0.36.0", "gix-fs", "gix-hash", "gix-lock", @@ -1574,9 +1574,9 @@ dependencies = [ [[package]] name = "gix-refspec" -version = "0.18.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0895cb7b1e70f3c3bd4550c329e9f5caf2975f97fcd4238e05754e72208ef61e" +checksum = "ccb0974cc41dbdb43a180c7f67aa481e1c1e160fcfa8f4a55291fd1126c1a6e7" dependencies = [ "bstr", "gix-hash", @@ -1588,9 +1588,9 @@ dependencies = [ [[package]] name = "gix-revision" -version = "0.22.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8c4b15cf2ab7a35f5bcb3ef146187c8d36df0177e171ca061913cbaaa890e89" +checksum = "2ca97ac73459a7f3766aa4a5638a6e37d56d4c7962bc1986fbaf4883d0772588" dependencies = [ "bstr", "gix-date", @@ -1604,9 +1604,9 @@ dependencies = [ [[package]] name = "gix-revwalk" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9870c6b1032f2084567710c3b2106ac603377f8d25766b8a6b7c33e6e3ca279" +checksum = "a16d8c892e4cd676d86f0265bf9d40cefd73d8d94f86b213b8b77d50e77efae0" dependencies = [ "gix-commitgraph", "gix-date", @@ -1623,7 +1623,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92b9542ac025a8c02ed5d17b3fc031a111a384e859d0be3532ec4d58c40a0f28" dependencies = [ - "bitflags 2.3.3", + "bitflags 2.4.0", "gix-path", "libc", "windows", @@ -1631,9 +1631,9 @@ dependencies = [ [[package]] name = "gix-submodule" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0150e82e9282d3f2ab2dd57a22f9f6c3447b9d9856e5321ac92d38e3e0e2b7" +checksum = "bba78c8d12aa24370178453ec3a472ff08dfaa657d116229f57f2c9cd469a1c2" dependencies = [ "bstr", "gix-config", @@ -1646,9 +1646,9 @@ dependencies = [ [[package]] name = "gix-tempfile" -version = "10.0.0" +version = "11.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ae0978f3e11dc57290ee75ac2477c815bca1ce2fa7ed5dc5f16db067410ac4d" +checksum = "05cc2205cf10d99f70b96e04e16c55d4c7cf33efc151df1f793e29fd12a931f8" dependencies = [ "gix-fs", "libc", @@ -1665,16 +1665,16 @@ checksum = "96b6d623a1152c3facb79067d6e2ecdae48130030cf27d6eb21109f13bd7b836" [[package]] name = "gix-transport" -version = "0.37.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ec726e6a245e68ace59a34126a1d679de60360676612985e70b0d3b102fb4e" +checksum = "2f209a93364e24f20319751bc11092272e2f3fe82bb72592b2822679cf5be752" dependencies = [ "base64", "bstr", "curl", "gix-command", "gix-credentials", - "gix-features", + "gix-features 0.36.0", "gix-packetline", "gix-quote", "gix-sec", @@ -1684,9 +1684,9 @@ dependencies = [ [[package]] name = "gix-traverse" -version = "0.33.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ef04ab3643acba289b5cedd25d6f53c0430770b1d689d1d654511e6fb81ba0" +checksum = "14d050ec7d4e1bb76abf0636cf4104fb915b70e54e3ced9a4427c999100ff38a" dependencies = [ "gix-commitgraph", "gix-date", @@ -1700,12 +1700,12 @@ dependencies = [ [[package]] name = "gix-url" -version = "0.24.0" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6125ecf46e8c68bf7202da6cad239831daebf0247ffbab30210d72f3856e420f" +checksum = "b1b9ac8ed32ad45f9fc6c5f8c0be2ed911e544a5a19afd62d95d524ebaa95671" dependencies = [ "bstr", - "gix-features", + "gix-features 0.36.0", "gix-path", "home 0.5.5", "thiserror", @@ -1733,13 +1733,13 @@ dependencies = [ [[package]] name = "gix-worktree" -version = "0.26.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f5e32972801bd82d56609e6fc84efc358fa1f11f25c5e83b7807ee2280f14fe" +checksum = "ddaf79e721dba64fe726a42f297a3c8ed42e55cdc0d81ca68452f2def3c2d7fd" dependencies = [ "bstr", "gix-attributes", - "gix-features", + "gix-features 0.36.0", "gix-fs", "gix-glob", "gix-hash", @@ -1856,7 +1856,7 @@ dependencies = [ [[package]] name = "home" -version = "0.5.7" +version = "0.5.8" dependencies = [ "windows-sys", ] @@ -1948,6 +1948,15 @@ dependencies = [ ] [[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] name = "itoa" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1961,9 +1970,9 @@ checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "jobserver" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" dependencies = [ "libc", ] @@ -1978,6 +1987,15 @@ dependencies = [ ] [[package]] +name = "kstring" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3066350882a1cd6d950d055997f379ac37fd39f81cd4d8ed186032eb3c5747" +dependencies = [ + "static_assertions", +] + +[[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1997,9 +2015,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.148" +version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" [[package]] name = "libgit2-sys" @@ -2017,9 +2035,9 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb" +checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" dependencies = [ "cfg-if", "windows-sys", @@ -2069,9 +2087,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.5" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" +checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" [[package]] name = "lock_api" @@ -2124,9 +2142,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.6.2" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5486aed0026218e61b8a01d5fbd5a0a134649abb71a0e53b7bc088529dced86e" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "memmap2" @@ -2263,7 +2281,7 @@ version = "0.10.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" dependencies = [ - "bitflags 2.3.3", + "bitflags 2.4.0", "cfg-if", "foreign-types", "libc", @@ -2280,7 +2298,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.38", ] [[package]] @@ -2378,7 +2396,7 @@ checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.3.5", "smallvec", "windows-targets", ] @@ -2475,7 +2493,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.38", ] [[package]] @@ -2566,9 +2584,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" dependencies = [ "unicode-ident", ] @@ -2584,19 +2602,19 @@ dependencies = [ [[package]] name = "proptest" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e35c06b98bf36aba164cc17cb25f7e232f5c4aeea73baa14b8a9f0d92dbfa65" +checksum = "7c003ac8c77cb07bb74f5f198bce836a689bcd5a42574612bf14d17bfd08c20e" dependencies = [ "bit-set", - "bitflags 1.3.2", - "byteorder", + "bit-vec", + "bitflags 2.4.0", "lazy_static", "num-traits", "rand", "rand_chacha", "rand_xorshift", - "regex-syntax 0.6.29", + "regex-syntax 0.7.2", "rusty-fork", "tempfile", "unarray", @@ -2714,6 +2732,15 @@ dependencies = [ ] [[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] name = "regex" version = "1.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2791,11 +2818,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.6" +version = "0.38.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee020b1716f0a80e2ace9b03441a749e402e86712f15f16fe8a8f75afac732f" +checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" dependencies = [ - "bitflags 2.3.3", + "bitflags 2.4.0", "errno", "libc", "linux-raw-sys", @@ -2883,9 +2910,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" dependencies = [ "serde", ] @@ -2899,9 +2926,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.188" +version = "1.0.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" dependencies = [ "serde_derive", ] @@ -2928,13 +2955,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.38", ] [[package]] @@ -2948,9 +2975,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.105" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa 1.0.6", "ryu", @@ -2959,18 +2986,18 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80" dependencies = [ "serde", ] [[package]] name = "sha1" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", @@ -2985,9 +3012,9 @@ checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" [[package]] name = "sha2" -version = "0.10.7" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", @@ -3043,9 +3070,9 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "snapbox" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b439536a42c43be148b610c7f7f968fb79a457254910a9cb20900da73cd3271" +checksum = "4b377c0b6e4715c116473d8e40d51e3fa5b0a2297ca9b2a931ba800667b259ed" dependencies = [ "anstream", "anstyle", @@ -3090,6 +3117,12 @@ dependencies = [ ] [[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3102,6 +3135,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] +name = "supports-hyperlinks" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84231692eb0d4d41e4cdd0cabfdd2e6cd9e255e65f80c9aa7c98dd502b4233d" +dependencies = [ + "is-terminal", +] + +[[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3114,9 +3156,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.29" +version = "2.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" dependencies = [ "proc-macro2", "quote", @@ -3147,13 +3189,13 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.8.0" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", + "redox_syscall 0.4.1", "rustix", "windows-sys", ] @@ -3170,22 +3212,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.47" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.47" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.38", ] [[package]] @@ -3255,9 +3297,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "toml" -version = "0.7.6" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" +checksum = "8ff9e3abce27ee2c9a37f9ad37238c1bdd4e789c84ba37df76aa4d528f5072cc" dependencies = [ "serde", "serde_spanned", @@ -3267,18 +3309,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.19.14" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" dependencies = [ "indexmap", "serde", @@ -3289,11 +3331,10 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -3301,20 +3342,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.38", ] [[package]] name = "tracing-core" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", "valuable", @@ -3405,9 +3446,9 @@ dependencies = [ [[package]] name = "unicode-width" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "unicode-xid" @@ -3548,9 +3589,9 @@ dependencies = [ [[package]] name = "walkdir" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", "winapi-util", @@ -3583,7 +3624,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.38", "wasm-bindgen-shared", ] @@ -3605,7 +3646,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.38", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3754,6 +3795,7 @@ dependencies = [ "cargo-util", "clap", "git2", + "semver", "tracing", "tracing-subscriber", ] diff --git a/src/tools/cargo/Cargo.toml b/src/tools/cargo/Cargo.toml index 440304416..3fb36b44e 100644 --- a/src/tools/cargo/Cargo.toml +++ b/src/tools/cargo/Cargo.toml @@ -11,38 +11,38 @@ exclude = [ ] [workspace.package] -rust-version = "1.72.0" +rust-version = "1.73" # MSRV:1 edition = "2021" license = "MIT OR Apache-2.0" [workspace.dependencies] -anstream = "0.6.3" +anstream = "0.6.4" anstyle = "1.0.4" anyhow = "1.0.75" -base64 = "0.21.3" +base64 = "0.21.5" bytesize = "1.3" cargo = { path = "" } -cargo-credential = { version = "0.4.0", path = "credential/cargo-credential" } -cargo-credential-libsecret = { version = "0.3.1", path = "credential/cargo-credential-libsecret" } -cargo-credential-wincred = { version = "0.3.0", path = "credential/cargo-credential-wincred" } -cargo-credential-macos-keychain = { version = "0.3.0", path = "credential/cargo-credential-macos-keychain" } +cargo-credential = { version = "0.4.1", path = "credential/cargo-credential" } +cargo-credential-libsecret = { version = "0.4.1", path = "credential/cargo-credential-libsecret" } +cargo-credential-macos-keychain = { version = "0.4.1", path = "credential/cargo-credential-macos-keychain" } +cargo-credential-wincred = { version = "0.4.1", path = "credential/cargo-credential-wincred" } cargo-platform = { path = "crates/cargo-platform", version = "0.1.4" } cargo-test-macro = { path = "crates/cargo-test-macro" } cargo-test-support = { path = "crates/cargo-test-support" } cargo-util = { version = "0.2.6", path = "crates/cargo-util" } -cargo_metadata = "0.17.0" -clap = "4.4.6" -color-print = "0.3.4" +cargo_metadata = "0.18.1" +clap = "4.4.7" +color-print = "0.3.5" core-foundation = { version = "0.9.3", features = ["mac_os_10_7_support"] } crates-io = { version = "0.39.0", path = "crates/crates-io" } criterion = { version = "0.5.1", features = ["html_reports"] } curl = "0.4.44" curl-sys = "0.4.68" filetime = "0.2.22" -flate2 = { version = "1.0.27", default-features = false, features = ["zlib"] } -git2 = "0.18.0" +flate2 = { version = "1.0.28", default-features = false, features = ["zlib"] } +git2 = "0.18.1" git2-curl = "0.19.0" -gix = { version = "0.54.1", default-features = false, features = ["blocking-http-transport-curl", "progress-tree", "revision"] } +gix = { version = "0.55.2", default-features = false, features = ["blocking-http-transport-curl", "progress-tree", "revision"] } gix-features-for-configuration-only = { version = "0.35.0", package = "gix-features", features = [ "parallel" ] } glob = "0.3.1" handlebars = { version = "3.5.5", features = ["dir_source"] } @@ -54,13 +54,13 @@ humantime = "2.1.0" ignore = "0.4.20" im-rc = "15.1.0" indexmap = "2" -itertools = "0.10.0" -jobserver = "0.1.26" +itertools = "0.11.0" +jobserver = "0.1.27" lazycell = "1.3.0" -libc = "0.2.148" +libc = "0.2.149" libgit2-sys = "0.16.1" -libloading = "0.8.0" -memchr = "2.6.2" +libloading = "0.8.1" +memchr = "2.6.4" miow = "0.6.0" opener = "0.6.1" openssl ="0.10.57" @@ -70,44 +70,46 @@ pathdiff = "0.2" percent-encoding = "2.3" pkg-config = "0.3.27" pretty_assertions = "1.4.0" -proptest = "1.2.0" +proptest = "1.3.1" pulldown-cmark = { version = "0.9.3", default-features = false } rand = "0.8.5" rustfix = "0.6.1" same-file = "1.0.6" security-framework = "2.9.2" -semver = { version = "1.0.18", features = ["serde"] } -serde = "1.0.188" +semver = { version = "1.0.20", features = ["serde"] } +serde = "1.0.190" serde-untagged = "0.1.1" serde-value = "0.7.0" serde_ignored = "0.1.9" -serde_json = "1.0.105" -sha1 = "0.10.5" -sha2 = "0.10.7" +serde_json = "1.0.108" +sha1 = "0.10.6" +sha2 = "0.10.8" shell-escape = "0.1.5" -snapbox = { version = "0.4.13", features = ["diff", "path"] } -syn = { version = "2.0.29", features = ["extra-traits", "full"] } +supports-hyperlinks = "2.1.0" +snapbox = { version = "0.4.14", features = ["diff", "path"] } +syn = { version = "2.0.38", features = ["extra-traits", "full"] } tar = { version = "0.4.40", default-features = false } -tempfile = "3.8.0" -thiserror = "1.0.47" +tempfile = "3.8.1" +thiserror = "1.0.50" time = { version = "0.3", features = ["parsing", "formatting", "serde"] } -toml = "0.7.6" -toml_edit = "0.19.14" -tracing = "0.1.37" +toml = "0.8.6" +toml_edit = { version = "0.20.7", features = ["serde"] } +tracing = "0.1.40" tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } unicase = "2.7.0" -unicode-width = "0.1.10" +unicode-width = "0.1.11" unicode-xid = "0.2.4" url = "2.4.1" varisat = "0.2.2" -walkdir = "2.3.3" +walkdir = "2.4.0" windows-sys = "0.48" [package] name = "cargo" -version = "0.75.0" +version = "0.76.0" edition.workspace = true license.workspace = true +rust-version.workspace = true homepage = "https://crates.io" repository = "https://github.com/rust-lang/cargo" documentation = "https://docs.rs/cargo" @@ -125,14 +127,11 @@ anstyle.workspace = true anyhow.workspace = true base64.workspace = true bytesize.workspace = true -cargo-platform.workspace = true cargo-credential.workspace = true -cargo-credential-libsecret.workspace = true -cargo-credential-macos-keychain.workspace = true -cargo-credential-wincred.workspace = true +cargo-platform.workspace = true cargo-util.workspace = true -color-print.workspace = true clap = { workspace = true, features = ["wrap_help"] } +color-print.workspace = true crates-io.workspace = true curl = { workspace = true, features = ["http2"] } curl-sys.workspace = true @@ -172,6 +171,7 @@ serde_ignored.workspace = true serde_json = { workspace = true, features = ["raw_value"] } sha1.workspace = true shell-escape.workspace = true +supports-hyperlinks.workspace = true syn.workspace = true tar.workspace = true tempfile.workspace = true @@ -186,9 +186,18 @@ unicode-xid.workspace = true url.workspace = true walkdir.workspace = true +[target.'cfg(target_os = "linux")'.dependencies] +cargo-credential-libsecret.workspace = true + +[target.'cfg(target_os = "macos")'.dependencies] +cargo-credential-macos-keychain.workspace = true + [target.'cfg(not(windows))'.dependencies] openssl = { workspace = true, optional = true } +[target.'cfg(windows)'.dependencies] +cargo-credential-wincred.workspace = true + [target.'cfg(windows)'.dependencies.windows-sys] workspace = true features = [ diff --git a/src/tools/cargo/ci/generate.py b/src/tools/cargo/ci/generate.py new file mode 100644 index 000000000..b750729dc --- /dev/null +++ b/src/tools/cargo/ci/generate.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 + +MAPPING = { + "build-script.html": "https://doc.rust-lang.org/cargo/reference/build-scripts.html", + "config.html": None, + "crates-io.html": "https://doc.rust-lang.org/cargo/reference/publishing.html", + "environment-variables.html": None, + "external-tools.html": None, + "faq.html": "https://doc.rust-lang.org/cargo/faq.html", + "guide.html": "https://doc.rust-lang.org/cargo/guide/", + "index.html": "https://doc.rust-lang.org/cargo/", + "manifest.html": None, + "pkgid-spec.html": None, + "policies.html": "https://crates.io/policies", + "source-replacement.html": None, + "specifying-dependencies.html": None, +} + +TEMPLATE = """\ +<html> +<head> +<meta http-equiv="refresh" content="0; url={mapped}" /> +<script> +window.location.replace("{mapped}" + window.location.hash); +</script> +<title>Page Moved</title> +</head> +<body> +This page has moved. Click <a href="{mapped}">here</a> to go to the new page. +</body> +</html> +""" + +def main(): + for name in sorted(MAPPING): + with open(name, 'w') as f: + mapped = MAPPING[name] + if mapped is None: + mapped = "https://doc.rust-lang.org/cargo/reference/{}".format(name) + f.write(TEMPLATE.format(name=name, mapped=mapped)) + + # WARN: The CNAME file is for GitHub to redirect requests to the custom domain. + # Missing this may entail security hazard and domain takeover. + # See <https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/managing-a-custom-domain-for-your-github-pages-site#securing-your-custom-domain> + with open('CNAME', 'w') as f: + f.write('doc.crates.io') + +if __name__ == '__main__': + main() diff --git a/src/tools/cargo/crates/cargo-platform/Cargo.toml b/src/tools/cargo/crates/cargo-platform/Cargo.toml index 016ead686..786948ff3 100644 --- a/src/tools/cargo/crates/cargo-platform/Cargo.toml +++ b/src/tools/cargo/crates/cargo-platform/Cargo.toml @@ -1,8 +1,9 @@ [package] name = "cargo-platform" -version = "0.1.5" +version = "0.1.6" edition.workspace = true license.workspace = true +rust-version = "1.70.0" # MSRV:3 homepage = "https://github.com/rust-lang/cargo" repository = "https://github.com/rust-lang/cargo" documentation = "https://docs.rs/cargo-platform" diff --git a/src/tools/cargo/crates/cargo-test-support/src/compare.rs b/src/tools/cargo/crates/cargo-test-support/src/compare.rs index 09e3a5a0c..d9e8d5454 100644 --- a/src/tools/cargo/crates/cargo-test-support/src/compare.rs +++ b/src/tools/cargo/crates/cargo-test-support/src/compare.rs @@ -236,6 +236,8 @@ fn substitute_macros(input: &str) -> String { ("[SKIPPING]", " Skipping"), ("[WAITING]", " Waiting"), ("[PUBLISHED]", " Published"), + ("[BLOCKING]", " Blocking"), + ("[GENERATED]", " Generated"), ]; let mut result = input.to_owned(); for &(pat, subst) in ¯os { diff --git a/src/tools/cargo/crates/cargo-test-support/src/diff.rs b/src/tools/cargo/crates/cargo-test-support/src/diff.rs index 3fedc839b..cd0c97385 100644 --- a/src/tools/cargo/crates/cargo-test-support/src/diff.rs +++ b/src/tools/cargo/crates/cargo-test-support/src/diff.rs @@ -132,7 +132,7 @@ pub fn render_colored_changes<T: fmt::Display>(changes: &[Change<T>]) -> String Change::Remove(i, s) => (format!("{:<4} ", i), '-', red, s), Change::Keep(x, y, s) => (format!("{:<4}{:<4} ", x, y), ' ', dim, s), }; - write!( + writeln!( buffer, "{dim}{nums}{reset}{bold}{sign}{reset}{color}{text}{reset}" ) diff --git a/src/tools/cargo/crates/cargo-test-support/src/lib.rs b/src/tools/cargo/crates/cargo-test-support/src/lib.rs index 1a8742720..ec74ce0b2 100644 --- a/src/tools/cargo/crates/cargo-test-support/src/lib.rs +++ b/src/tools/cargo/crates/cargo-test-support/src/lib.rs @@ -13,6 +13,7 @@ use std::path::{Path, PathBuf}; use std::process::{Command, Output}; use std::str; use std::sync::OnceLock; +use std::thread::JoinHandle; use std::time::{self, Duration}; use anyhow::{bail, Result}; @@ -1470,3 +1471,50 @@ pub fn symlink_supported() -> bool { pub fn no_such_file_err_msg() -> String { std::io::Error::from_raw_os_error(2).to_string() } + +/// Helper to retry a function `n` times. +/// +/// The function should return `Some` when it is ready. +pub fn retry<F, R>(n: u32, mut f: F) -> R +where + F: FnMut() -> Option<R>, +{ + let mut count = 0; + let start = std::time::Instant::now(); + loop { + if let Some(r) = f() { + return r; + } + count += 1; + if count > n { + panic!( + "test did not finish within {n} attempts ({:?} total)", + start.elapsed() + ); + } + sleep_ms(100); + } +} + +#[test] +#[should_panic(expected = "test did not finish")] +fn retry_fails() { + retry(2, || None::<()>); +} + +/// Helper that waits for a thread to finish, up to `n` tenths of a second. +pub fn thread_wait_timeout<T>(n: u32, thread: JoinHandle<T>) -> T { + retry(n, || thread.is_finished().then_some(())); + thread.join().unwrap() +} + +/// Helper that runs some function, and waits up to `n` tenths of a second for +/// it to finish. +pub fn threaded_timeout<F, R>(n: u32, f: F) -> R +where + F: FnOnce() -> R + Send + 'static, + R: Send + 'static, +{ + let thread = std::thread::spawn(|| f()); + thread_wait_timeout(n, thread) +} diff --git a/src/tools/cargo/crates/cargo-util/Cargo.toml b/src/tools/cargo/crates/cargo-util/Cargo.toml index cba00f917..616a79c5e 100644 --- a/src/tools/cargo/crates/cargo-util/Cargo.toml +++ b/src/tools/cargo/crates/cargo-util/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo-util" -version = "0.2.7" +version = "0.2.8" rust-version.workspace = true edition.workspace = true license.workspace = true @@ -10,12 +10,12 @@ description = "Miscellaneous support code used by Cargo." [dependencies] anyhow.workspace = true -sha2.workspace = true filetime.workspace = true hex.workspace = true jobserver.workspace = true libc.workspace = true same-file.workspace = true +sha2.workspace = true shell-escape.workspace = true tempfile.workspace = true tracing.workspace = true diff --git a/src/tools/cargo/crates/cargo-util/src/paths.rs b/src/tools/cargo/crates/cargo-util/src/paths.rs index 888ca1af5..f405c8f97 100644 --- a/src/tools/cargo/crates/cargo-util/src/paths.rs +++ b/src/tools/cargo/crates/cargo-util/src/paths.rs @@ -180,6 +180,19 @@ pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> .with_context(|| format!("failed to write `{}`", path.display())) } +/// Writes a file to disk atomically. +/// +/// write_atomic uses tempfile::persist to accomplish atomic writes. +pub fn write_atomic<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> { + let path = path.as_ref(); + let mut tmp = TempFileBuilder::new() + .prefix(path.file_name().unwrap()) + .tempfile_in(path.parent().unwrap())?; + tmp.write_all(contents.as_ref())?; + tmp.persist(path)?; + Ok(()) +} + /// Equivalent to [`write()`], but does not write anything if the file contents /// are identical to the given contents. pub fn write_if_changed<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> { @@ -681,7 +694,8 @@ pub fn create_dir_all_excluded_from_backups_atomic(p: impl AsRef<Path>) -> Resul // we can infer from it's another cargo process doing work. if let Err(e) = fs::rename(tempdir.path(), path) { if !path.exists() { - return Err(anyhow::Error::from(e)); + return Err(anyhow::Error::from(e)) + .with_context(|| format!("failed to create directory `{}`", path.display())); } } Ok(()) @@ -775,6 +789,29 @@ fn exclude_from_time_machine(path: &Path) { #[cfg(test)] mod tests { use super::join_paths; + use super::write; + use super::write_atomic; + + #[test] + fn write_works() { + let original_contents = "[dependencies]\nfoo = 0.1.0"; + + let tmpdir = tempfile::tempdir().unwrap(); + let path = tmpdir.path().join("Cargo.toml"); + write(&path, original_contents).unwrap(); + let contents = std::fs::read_to_string(&path).unwrap(); + assert_eq!(contents, original_contents); + } + #[test] + fn write_atomic_works() { + let original_contents = "[dependencies]\nfoo = 0.1.0"; + + let tmpdir = tempfile::tempdir().unwrap(); + let path = tmpdir.path().join("Cargo.toml"); + write_atomic(&path, original_contents).unwrap(); + let contents = std::fs::read_to_string(&path).unwrap(); + assert_eq!(contents, original_contents); + } #[test] fn join_paths_lists_paths_on_error() { diff --git a/src/tools/cargo/crates/crates-io/Cargo.toml b/src/tools/cargo/crates/crates-io/Cargo.toml index d06dacdfa..f1b92602e 100644 --- a/src/tools/cargo/crates/crates-io/Cargo.toml +++ b/src/tools/cargo/crates/crates-io/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "crates-io" -version = "0.39.0" +version = "0.39.1" rust-version.workspace = true edition.workspace = true license.workspace = true diff --git a/src/tools/cargo/crates/crates-io/lib.rs b/src/tools/cargo/crates/crates-io/lib.rs index 757241fd3..1764ce527 100644 --- a/src/tools/cargo/crates/crates-io/lib.rs +++ b/src/tools/cargo/crates/crates-io/lib.rs @@ -38,6 +38,10 @@ pub struct Crate { pub max_version: String, } +/// This struct is serialized as JSON and sent as metadata ahead of the crate +/// tarball when publishing crates to a crate registry like crates.io. +/// +/// see <https://doc.rust-lang.org/cargo/reference/registry-web-api.html#publish> #[derive(Serialize, Deserialize)] pub struct NewCrate { pub name: String, diff --git a/src/tools/cargo/crates/home/Cargo.toml b/src/tools/cargo/crates/home/Cargo.toml index 03bd555a2..702a14e55 100644 --- a/src/tools/cargo/crates/home/Cargo.toml +++ b/src/tools/cargo/crates/home/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "home" -version = "0.5.7" # also update `html_root_url` in `src/lib.rs` +version = "0.5.8" authors = ["Brian Anderson <andersrb@gmail.com>"] +rust-version = "1.70.0" # MSRV:3 documentation = "https://docs.rs/home" edition.workspace = true include = [ diff --git a/src/tools/cargo/crates/home/src/lib.rs b/src/tools/cargo/crates/home/src/lib.rs index 0e1e975e4..4aee7383b 100644 --- a/src/tools/cargo/crates/home/src/lib.rs +++ b/src/tools/cargo/crates/home/src/lib.rs @@ -18,7 +18,6 @@ //! //! [discussion]: https://github.com/rust-lang/rust/pull/46799#issuecomment-361156935 -#![doc(html_root_url = "https://docs.rs/home/0.5.6")] #![deny(rust_2018_idioms)] pub mod env; diff --git a/src/tools/cargo/crates/resolver-tests/src/lib.rs b/src/tools/cargo/crates/resolver-tests/src/lib.rs index 9bdeb8674..e2cbcee62 100644 --- a/src/tools/cargo/crates/resolver-tests/src/lib.rs +++ b/src/tools/cargo/crates/resolver-tests/src/lib.rs @@ -12,7 +12,7 @@ use std::task::Poll; use std::time::Instant; use cargo::core::dependency::DepKind; -use cargo::core::resolver::{self, ResolveOpts, VersionPreferences}; +use cargo::core::resolver::{self, ResolveOpts, VersionOrdering, VersionPreferences}; use cargo::core::Resolve; use cargo::core::{Dependency, PackageId, Registry, Summary}; use cargo::core::{GitReference, SourceId}; @@ -190,15 +190,17 @@ pub fn resolve_with_config_raw( .unwrap(); let opts = ResolveOpts::everything(); let start = Instant::now(); - let max_rust_version = None; + let mut version_prefs = VersionPreferences::default(); + if config.cli_unstable().minimal_versions { + version_prefs.version_ordering(VersionOrdering::MinimumVersionsFirst) + } let resolve = resolver::resolve( &[(summary, opts)], &[], &mut registry, - &VersionPreferences::default(), + &version_prefs, Some(config), true, - max_rust_version, ); // The largest test in our suite takes less then 30 sec. @@ -982,14 +984,17 @@ fn meta_test_multiple_versions_strategy() { /// Assert `xs` contains `elems` #[track_caller] -pub fn assert_contains<A: PartialEq>(xs: &[A], elems: &[A]) { +pub fn assert_contains<A: PartialEq + std::fmt::Debug>(xs: &[A], elems: &[A]) { for elem in elems { - assert!(xs.contains(elem)); + assert!( + xs.contains(elem), + "missing element\nset: {xs:?}\nmissing: {elem:?}" + ); } } #[track_caller] -pub fn assert_same<A: PartialEq>(a: &[A], b: &[A]) { - assert_eq!(a.len(), b.len()); +pub fn assert_same<A: PartialEq + std::fmt::Debug>(a: &[A], b: &[A]) { + assert_eq!(a.len(), b.len(), "not equal\n{a:?}\n{b:?}"); assert_contains(b, a); } diff --git a/src/tools/cargo/crates/xtask-bump-check/Cargo.toml b/src/tools/cargo/crates/xtask-bump-check/Cargo.toml index e878f7dda..c8a472adc 100644 --- a/src/tools/cargo/crates/xtask-bump-check/Cargo.toml +++ b/src/tools/cargo/crates/xtask-bump-check/Cargo.toml @@ -11,5 +11,6 @@ cargo.workspace = true cargo-util.workspace = true clap.workspace = true git2.workspace = true -tracing.workspace = true +semver.workspace = true tracing-subscriber.workspace = true +tracing.workspace = true diff --git a/src/tools/cargo/crates/xtask-bump-check/src/xtask.rs b/src/tools/cargo/crates/xtask-bump-check/src/xtask.rs index 4bf3f03d5..b99ac8b32 100644 --- a/src/tools/cargo/crates/xtask-bump-check/src/xtask.rs +++ b/src/tools/cargo/crates/xtask-bump-check/src/xtask.rs @@ -22,8 +22,8 @@ use cargo::core::Registry; use cargo::core::SourceId; use cargo::core::Workspace; use cargo::sources::source::QueryKind; +use cargo::util::cache_lock::CacheLockMode; use cargo::util::command_prelude::*; -use cargo::util::ToSemver; use cargo::CargoResult; use cargo_util::ProcessBuilder; @@ -148,26 +148,13 @@ fn bump_check(args: &clap::ArgMatches, config: &cargo::util::Config) -> CargoRes anyhow::bail!(msg) } - // Tracked by https://github.com/obi1kenobi/cargo-semver-checks/issues/511 - let exclude_args = [ - "--exclude", - "cargo-credential-1password", - "--exclude", - "cargo-credential-libsecret", - "--exclude", - "cargo-credential-macos-keychain", - "--exclude", - "cargo-credential-wincred", - ]; - // Even when we test against baseline-rev, we still need to make sure a // change doesn't violate SemVer rules against crates.io releases. The // possibility of this happening is nearly zero but no harm to check twice. let mut cmd = ProcessBuilder::new("cargo"); cmd.arg("semver-checks") .arg("check-release") - .arg("--workspace") - .args(&exclude_args); + .arg("--workspace"); config.shell().status("Running", &cmd)?; cmd.exec()?; @@ -176,8 +163,7 @@ fn bump_check(args: &clap::ArgMatches, config: &cargo::util::Config) -> CargoRes cmd.arg("semver-checks") .arg("--workspace") .arg("--baseline-rev") - .arg(referenced_commit.id().to_string()) - .args(&exclude_args); + .arg(referenced_commit.id().to_string()); config.shell().status("Running", &cmd)?; cmd.exec()?; } @@ -290,7 +276,7 @@ fn beta_and_stable_branch(repo: &git2::Repository) -> CargoResult<[git2::Branch< tracing::trace!("branch `{name}` is not in the format of `<remote>/rust-<semver>`"); continue; }; - let Ok(version) = version.to_semver() else { + let Ok(version) = version.parse::<semver::Version>() else { tracing::trace!("branch `{name}` is not a valid semver: `{version}`"); continue; }; @@ -361,7 +347,7 @@ fn check_crates_io<'a>( ) -> CargoResult<()> { let source_id = SourceId::crates_io(config)?; let mut registry = PackageRegistry::new(config)?; - let _lock = config.acquire_package_cache_lock()?; + let _lock = config.acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?; registry.lock_patches(); config.shell().status( STATUS, diff --git a/src/tools/cargo/credential/cargo-credential-1password/Cargo.toml b/src/tools/cargo/credential/cargo-credential-1password/Cargo.toml index d7bd949d1..9e5b1e635 100644 --- a/src/tools/cargo/credential/cargo-credential-1password/Cargo.toml +++ b/src/tools/cargo/credential/cargo-credential-1password/Cargo.toml @@ -1,8 +1,9 @@ [package] name = "cargo-credential-1password" -version = "0.4.0" +version = "0.4.2" edition.workspace = true license.workspace = true +rust-version = "1.70.0" # MSRV:3 repository = "https://github.com/rust-lang/cargo" description = "A Cargo credential process that stores tokens in a 1password vault." diff --git a/src/tools/cargo/credential/cargo-credential-1password/LICENSE-APACHE b/src/tools/cargo/credential/cargo-credential-1password/LICENSE-APACHE new file mode 120000 index 000000000..1cd601d0a --- /dev/null +++ b/src/tools/cargo/credential/cargo-credential-1password/LICENSE-APACHE @@ -0,0 +1 @@ +../../LICENSE-APACHE
\ No newline at end of file diff --git a/src/tools/cargo/credential/cargo-credential-1password/LICENSE-MIT b/src/tools/cargo/credential/cargo-credential-1password/LICENSE-MIT new file mode 120000 index 000000000..b2cfbdc7b --- /dev/null +++ b/src/tools/cargo/credential/cargo-credential-1password/LICENSE-MIT @@ -0,0 +1 @@ +../../LICENSE-MIT
\ No newline at end of file diff --git a/src/tools/cargo/credential/cargo-credential-1password/README.md b/src/tools/cargo/credential/cargo-credential-1password/README.md index 3648efe4b..fc3c9460a 100644 --- a/src/tools/cargo/credential/cargo-credential-1password/README.md +++ b/src/tools/cargo/credential/cargo-credential-1password/README.md @@ -2,17 +2,31 @@ A Cargo [credential provider] for [1password]. -`cargo-credential-1password` uses the 1password `op` CLI to store the token. You must -install the `op` CLI from the [1password -website](https://1password.com/downloads/command-line/). You must run `op signin` -at least once with the appropriate arguments (such as `op signin my.1password.com user@example.com`), -unless you provide the sign-in-address and email arguments. The master password will be required on each request -unless the appropriate `OP_SESSION` environment variable is set. It supports -the following command-line arguments: -* `--account`: The account shorthand name to use. -* `--vault`: The vault name to use. -* `--sign-in-address`: The sign-in-address, which is a web address such as `my.1password.com`. -* `--email`: The email address to sign in with. +## Usage + +`cargo-credential-1password` uses the 1password `op` CLI to store the token. You +must install the `op` CLI from the [1password +website](https://1password.com/downloads/command-line/). + +Afterward you need to configure `cargo` to use `cargo-credential-1password` as +the credential provider. You can do this by adding something like the following +to your [cargo config file][credential provider]: + +```toml +[registry] +global-credential-providers = ["cargo-credential-1password --account my.1password.com"] +``` + +Finally, run `cargo login` to save your registry token in 1password. + +## CLI Arguments + +`cargo-credential-1password` supports the following command-line arguments: + +* `--account`: The account name to use. For a list of available accounts, + run `op account list`. +* `--vault`: The vault name to use. For a list of available vaults, + run `op vault list`. [1password]: https://1password.com/ -[credential provider]: https://doc.rust-lang.org/nightly/cargo/reference/registry-authentication.html +[credential provider]: https://doc.rust-lang.org/stable/cargo/reference/registry-authentication.html diff --git a/src/tools/cargo/credential/cargo-credential-1password/src/main.rs b/src/tools/cargo/credential/cargo-credential-1password/src/main.rs index 921b52145..321a99c51 100644 --- a/src/tools/cargo/credential/cargo-credential-1password/src/main.rs +++ b/src/tools/cargo/credential/cargo-credential-1password/src/main.rs @@ -79,6 +79,10 @@ impl OnePasswordKeychain { } let mut cmd = Command::new("op"); cmd.args(["signin", "--raw"]); + if let Some(account) = &self.account { + cmd.arg("--account"); + cmd.arg(account); + } cmd.stdout(Stdio::piped()); let mut child = cmd .spawn() diff --git a/src/tools/cargo/credential/cargo-credential-libsecret/Cargo.toml b/src/tools/cargo/credential/cargo-credential-libsecret/Cargo.toml index 5bedad3b9..19ef33a34 100644 --- a/src/tools/cargo/credential/cargo-credential-libsecret/Cargo.toml +++ b/src/tools/cargo/credential/cargo-credential-libsecret/Cargo.toml @@ -1,8 +1,9 @@ [package] name = "cargo-credential-libsecret" -version = "0.3.2" +version = "0.4.1" edition.workspace = true license.workspace = true +rust-version.workspace = true repository = "https://github.com/rust-lang/cargo" description = "A Cargo credential process that stores tokens with GNOME libsecret." diff --git a/src/tools/cargo/credential/cargo-credential-libsecret/LICENSE-APACHE b/src/tools/cargo/credential/cargo-credential-libsecret/LICENSE-APACHE new file mode 120000 index 000000000..1cd601d0a --- /dev/null +++ b/src/tools/cargo/credential/cargo-credential-libsecret/LICENSE-APACHE @@ -0,0 +1 @@ +../../LICENSE-APACHE
\ No newline at end of file diff --git a/src/tools/cargo/credential/cargo-credential-libsecret/LICENSE-MIT b/src/tools/cargo/credential/cargo-credential-libsecret/LICENSE-MIT new file mode 120000 index 000000000..b2cfbdc7b --- /dev/null +++ b/src/tools/cargo/credential/cargo-credential-libsecret/LICENSE-MIT @@ -0,0 +1 @@ +../../LICENSE-MIT
\ No newline at end of file diff --git a/src/tools/cargo/credential/cargo-credential-macos-keychain/Cargo.toml b/src/tools/cargo/credential/cargo-credential-macos-keychain/Cargo.toml index 172e9c10b..4dec8def6 100644 --- a/src/tools/cargo/credential/cargo-credential-macos-keychain/Cargo.toml +++ b/src/tools/cargo/credential/cargo-credential-macos-keychain/Cargo.toml @@ -1,8 +1,9 @@ [package] name = "cargo-credential-macos-keychain" -version = "0.3.1" +version = "0.4.1" edition.workspace = true license.workspace = true +rust-version.workspace = true repository = "https://github.com/rust-lang/cargo" description = "A Cargo credential process that stores tokens in a macOS keychain." diff --git a/src/tools/cargo/credential/cargo-credential-macos-keychain/LICENSE-APACHE b/src/tools/cargo/credential/cargo-credential-macos-keychain/LICENSE-APACHE new file mode 120000 index 000000000..1cd601d0a --- /dev/null +++ b/src/tools/cargo/credential/cargo-credential-macos-keychain/LICENSE-APACHE @@ -0,0 +1 @@ +../../LICENSE-APACHE
\ No newline at end of file diff --git a/src/tools/cargo/credential/cargo-credential-macos-keychain/LICENSE-MIT b/src/tools/cargo/credential/cargo-credential-macos-keychain/LICENSE-MIT new file mode 120000 index 000000000..b2cfbdc7b --- /dev/null +++ b/src/tools/cargo/credential/cargo-credential-macos-keychain/LICENSE-MIT @@ -0,0 +1 @@ +../../LICENSE-MIT
\ No newline at end of file diff --git a/src/tools/cargo/credential/cargo-credential-wincred/Cargo.toml b/src/tools/cargo/credential/cargo-credential-wincred/Cargo.toml index 6da6578a5..c904075bb 100644 --- a/src/tools/cargo/credential/cargo-credential-wincred/Cargo.toml +++ b/src/tools/cargo/credential/cargo-credential-wincred/Cargo.toml @@ -1,8 +1,9 @@ [package] name = "cargo-credential-wincred" -version = "0.3.1" +version = "0.4.1" edition.workspace = true license.workspace = true +rust-version.workspace = true repository = "https://github.com/rust-lang/cargo" description = "A Cargo credential process that stores tokens with Windows Credential Manager." diff --git a/src/tools/cargo/credential/cargo-credential-wincred/LICENSE-APACHE b/src/tools/cargo/credential/cargo-credential-wincred/LICENSE-APACHE new file mode 120000 index 000000000..1cd601d0a --- /dev/null +++ b/src/tools/cargo/credential/cargo-credential-wincred/LICENSE-APACHE @@ -0,0 +1 @@ +../../LICENSE-APACHE
\ No newline at end of file diff --git a/src/tools/cargo/credential/cargo-credential-wincred/LICENSE-MIT b/src/tools/cargo/credential/cargo-credential-wincred/LICENSE-MIT new file mode 120000 index 000000000..b2cfbdc7b --- /dev/null +++ b/src/tools/cargo/credential/cargo-credential-wincred/LICENSE-MIT @@ -0,0 +1 @@ +../../LICENSE-MIT
\ No newline at end of file diff --git a/src/tools/cargo/credential/cargo-credential/Cargo.toml b/src/tools/cargo/credential/cargo-credential/Cargo.toml index c8db996bf..8ba65b8b9 100644 --- a/src/tools/cargo/credential/cargo-credential/Cargo.toml +++ b/src/tools/cargo/credential/cargo-credential/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "cargo-credential" -version = "0.4.0" +version = "0.4.1" edition.workspace = true license.workspace = true -rust-version = "1.70.0" +rust-version = "1.70.0" # MSRV:3 repository = "https://github.com/rust-lang/cargo" description = "A library to assist writing Cargo credential helpers." diff --git a/src/tools/cargo/credential/cargo-credential/LICENSE-APACHE b/src/tools/cargo/credential/cargo-credential/LICENSE-APACHE new file mode 120000 index 000000000..1cd601d0a --- /dev/null +++ b/src/tools/cargo/credential/cargo-credential/LICENSE-APACHE @@ -0,0 +1 @@ +../../LICENSE-APACHE
\ No newline at end of file diff --git a/src/tools/cargo/credential/cargo-credential/LICENSE-MIT b/src/tools/cargo/credential/cargo-credential/LICENSE-MIT new file mode 120000 index 000000000..b2cfbdc7b --- /dev/null +++ b/src/tools/cargo/credential/cargo-credential/LICENSE-MIT @@ -0,0 +1 @@ +../../LICENSE-MIT
\ No newline at end of file diff --git a/src/tools/cargo/src/bin/cargo/cli.rs b/src/tools/cargo/src/bin/cargo/cli.rs index 06b4a20e2..a21030f01 100644 --- a/src/tools/cargo/src/bin/cargo/cli.rs +++ b/src/tools/cargo/src/bin/cargo/cli.rs @@ -2,7 +2,7 @@ use anyhow::{anyhow, Context as _}; use cargo::core::shell::Shell; use cargo::core::{features, CliUnstable}; use cargo::{self, drop_print, drop_println, CargoResult, CliResult, Config}; -use clap::{Arg, ArgMatches}; +use clap::{builder::UnknownArgumentValueParser, Arg, ArgMatches}; use itertools::Itertools; use std::collections::HashMap; use std::ffi::OsStr; @@ -618,15 +618,29 @@ See '<cyan,bold>cargo help</> <cyan><<command>></>' for more information on a sp .help_heading(heading::MANIFEST_OPTIONS) .global(true), ) + // Better suggestion for the unsupported short config flag. + .arg( Arg::new("unsupported-short-config-flag") + .help("") + .short('c') + .value_parser(UnknownArgumentValueParser::suggest_arg("--config")) + .action(ArgAction::SetTrue) + .global(true) + .hide(true)) .arg(multi_opt("config", "KEY=VALUE", "Override a configuration value").global(true)) - .arg( - Arg::new("unstable-features") - .help("Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details") - .short('Z') - .value_name("FLAG") - .action(ArgAction::Append) - .global(true), - ) + // Better suggestion for the unsupported lowercase unstable feature flag. + .arg( Arg::new("unsupported-lowercase-unstable-feature-flag") + .help("") + .short('z') + .value_parser(UnknownArgumentValueParser::suggest_arg("-Z")) + .action(ArgAction::SetTrue) + .global(true) + .hide(true)) + .arg(Arg::new("unstable-features") + .help("Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details") + .short('Z') + .value_name("FLAG") + .action(ArgAction::Append) + .global(true)) .subcommands(commands::builtin()) } diff --git a/src/tools/cargo/src/bin/cargo/commands/add.rs b/src/tools/cargo/src/bin/cargo/commands/add.rs index 344cd2af3..e1ece14b8 100644 --- a/src/tools/cargo/src/bin/cargo/commands/add.rs +++ b/src/tools/cargo/src/bin/cargo/commands/add.rs @@ -77,7 +77,7 @@ Example uses: "Ignore `rust-version` specification in packages (unstable)" ), ]) - .arg_manifest_path() + .arg_manifest_path_without_unsupported_path_tip() .arg_package("Package to modify") .arg_dry_run("Don't actually write the manifest") .arg_quiet() diff --git a/src/tools/cargo/src/bin/cargo/commands/bench.rs b/src/tools/cargo/src/bin/cargo/commands/bench.rs index 1a97d84a4..85c975a6c 100644 --- a/src/tools/cargo/src/bin/cargo/commands/bench.rs +++ b/src/tools/cargo/src/bin/cargo/commands/bench.rs @@ -12,6 +12,7 @@ pub fn cli() -> Command { ) .arg( Arg::new("args") + .value_name("ARGS") .help("Arguments for the bench binary") .num_args(0..) .last(true), @@ -36,9 +37,9 @@ pub fn cli() -> Command { "Benchmark only the specified example", "Benchmark all examples", "Benchmark only the specified test target", - "Benchmark all tests", + "Benchmark all test targets", "Benchmark only the specified bench target", - "Benchmark all benches", + "Benchmark all bench targets", "Benchmark all targets", ) .arg_features() diff --git a/src/tools/cargo/src/bin/cargo/commands/build.rs b/src/tools/cargo/src/bin/cargo/commands/build.rs index d503f43dd..e2ed87d1b 100644 --- a/src/tools/cargo/src/bin/cargo/commands/build.rs +++ b/src/tools/cargo/src/bin/cargo/commands/build.rs @@ -23,9 +23,9 @@ pub fn cli() -> Command { "Build only the specified example", "Build all examples", "Build only the specified test target", - "Build all tests", + "Build all test targets", "Build only the specified bench target", - "Build all benches", + "Build all bench targets", "Build all targets", ) .arg_features() @@ -35,14 +35,7 @@ pub fn cli() -> Command { .arg_parallel() .arg_target_triple("Build for the target triple") .arg_target_dir() - .arg( - opt( - "out-dir", - "Copy final artifacts to this directory (unstable)", - ) - .value_name("PATH") - .help_heading(heading::COMPILATION_OPTIONS), - ) + .arg_out_dir() .arg_build_plan() .arg_unit_graph() .arg_timings() diff --git a/src/tools/cargo/src/bin/cargo/commands/check.rs b/src/tools/cargo/src/bin/cargo/commands/check.rs index a54e84cdc..77e2b9280 100644 --- a/src/tools/cargo/src/bin/cargo/commands/check.rs +++ b/src/tools/cargo/src/bin/cargo/commands/check.rs @@ -23,9 +23,9 @@ pub fn cli() -> Command { "Check only the specified example", "Check all examples", "Check only the specified test target", - "Check all tests", + "Check all test targets", "Check only the specified bench target", - "Check all benches", + "Check all bench targets", "Check all targets", ) .arg_features() diff --git a/src/tools/cargo/src/bin/cargo/commands/fix.rs b/src/tools/cargo/src/bin/cargo/commands/fix.rs index 0ecf47450..bd938dbc7 100644 --- a/src/tools/cargo/src/bin/cargo/commands/fix.rs +++ b/src/tools/cargo/src/bin/cargo/commands/fix.rs @@ -41,9 +41,9 @@ pub fn cli() -> Command { "Fix only the specified example", "Fix all examples", "Fix only the specified test target", - "Fix all tests", + "Fix all test targets", "Fix only the specified bench target", - "Fix all benches", + "Fix all bench targets", "Fix all targets (default)", ) .arg_features() diff --git a/src/tools/cargo/src/bin/cargo/commands/init.rs b/src/tools/cargo/src/bin/cargo/commands/init.rs index 48409d827..04dd7ae45 100644 --- a/src/tools/cargo/src/bin/cargo/commands/init.rs +++ b/src/tools/cargo/src/bin/cargo/commands/init.rs @@ -5,7 +5,12 @@ use cargo::ops; pub fn cli() -> Command { subcommand("init") .about("Create a new cargo package in an existing directory") - .arg(Arg::new("path").action(ArgAction::Set).default_value(".")) + .arg( + Arg::new("path") + .value_name("PATH") + .action(ArgAction::Set) + .default_value("."), + ) .arg_new_opts() .arg_registry("Registry to use") .arg_quiet() diff --git a/src/tools/cargo/src/bin/cargo/commands/install.rs b/src/tools/cargo/src/bin/cargo/commands/install.rs index 2e5163bdc..cb66ba100 100644 --- a/src/tools/cargo/src/bin/cargo/commands/install.rs +++ b/src/tools/cargo/src/bin/cargo/commands/install.rs @@ -6,9 +6,9 @@ use anyhow::format_err; use cargo::core::{GitReference, SourceId, Workspace}; use cargo::ops; use cargo::util::IntoUrl; -use cargo::util::ToSemver; -use cargo::util::VersionReqExt; +use cargo::util_semver::VersionExt; use cargo::CargoResult; +use itertools::Itertools; use semver::VersionReq; use cargo_util::paths; @@ -16,7 +16,13 @@ use cargo_util::paths; pub fn cli() -> Command { subcommand("install") .about("Install a Rust binary. Default location is $HOME/.cargo/bin") - .arg(Arg::new("crate").value_parser(parse_crate).num_args(0..)) + .arg( + Arg::new("crate") + .value_name("CRATE[@<VER>]") + .help("Select the package from the given source") + .value_parser(parse_crate) + .num_args(0..), + ) .arg( opt("version", "Specify a version to install") .alias("vers") @@ -113,6 +119,7 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult { .get_many::<CrateVersion>("crate") .unwrap_or_default() .cloned() + .dedup_by(|x, y| x == y) .map(|(krate, local_version)| resolve_crate(krate, local_version, version)) .collect::<crate::CargoResult<Vec<_>>>()?; @@ -256,8 +263,8 @@ fn parse_semver_flag(v: &str) -> CargoResult<VersionReq> { ), } } else { - match v.to_semver() { - Ok(v) => Ok(VersionReq::exact(&v)), + match v.trim().parse::<semver::Version>() { + Ok(v) => Ok(v.to_exact_req()), Err(e) => { let mut msg = e.to_string(); diff --git a/src/tools/cargo/src/bin/cargo/commands/login.rs b/src/tools/cargo/src/bin/cargo/commands/login.rs index 118b03310..877ec6aeb 100644 --- a/src/tools/cargo/src/bin/cargo/commands/login.rs +++ b/src/tools/cargo/src/bin/cargo/commands/login.rs @@ -6,7 +6,7 @@ use crate::command_prelude::*; pub fn cli() -> Command { subcommand("login") .about("Log in to a registry.") - .arg(Arg::new("token").action(ArgAction::Set)) + .arg(Arg::new("token").value_name("TOKEN").action(ArgAction::Set)) .arg_registry("Registry to use") .arg( Arg::new("args") diff --git a/src/tools/cargo/src/bin/cargo/commands/new.rs b/src/tools/cargo/src/bin/cargo/commands/new.rs index c8d19218f..0ab093012 100644 --- a/src/tools/cargo/src/bin/cargo/commands/new.rs +++ b/src/tools/cargo/src/bin/cargo/commands/new.rs @@ -5,7 +5,12 @@ use cargo::ops; pub fn cli() -> Command { subcommand("new") .about("Create a new cargo package at <path>") - .arg(Arg::new("path").action(ArgAction::Set).required(true)) + .arg( + Arg::new("path") + .value_name("PATH") + .action(ArgAction::Set) + .required(true), + ) .arg_new_opts() .arg_registry("Registry to use") .arg_quiet() diff --git a/src/tools/cargo/src/bin/cargo/commands/owner.rs b/src/tools/cargo/src/bin/cargo/commands/owner.rs index 00080b829..b787d094c 100644 --- a/src/tools/cargo/src/bin/cargo/commands/owner.rs +++ b/src/tools/cargo/src/bin/cargo/commands/owner.rs @@ -6,7 +6,7 @@ use cargo_credential::Secret; pub fn cli() -> Command { subcommand("owner") .about("Manage the owners of a crate on the registry") - .arg(Arg::new("crate").action(ArgAction::Set)) + .arg(Arg::new("crate").value_name("CRATE").action(ArgAction::Set)) .arg( multi_opt( "add", diff --git a/src/tools/cargo/src/bin/cargo/commands/pkgid.rs b/src/tools/cargo/src/bin/cargo/commands/pkgid.rs index 9d7a6c712..2d1d41325 100644 --- a/src/tools/cargo/src/bin/cargo/commands/pkgid.rs +++ b/src/tools/cargo/src/bin/cargo/commands/pkgid.rs @@ -6,7 +6,7 @@ use cargo::util::print_available_packages; pub fn cli() -> Command { subcommand("pkgid") .about("Print a fully qualified package specification") - .arg(Arg::new("spec").action(ArgAction::Set)) + .arg(Arg::new("spec").value_name("SPEC").action(ArgAction::Set)) .arg_quiet() .arg_package("Argument to get the package ID specifier for") .arg_manifest_path() diff --git a/src/tools/cargo/src/bin/cargo/commands/remove.rs b/src/tools/cargo/src/bin/cargo/commands/remove.rs index c6508a6b2..c115291cb 100644 --- a/src/tools/cargo/src/bin/cargo/commands/remove.rs +++ b/src/tools/cargo/src/bin/cargo/commands/remove.rs @@ -34,19 +34,19 @@ pub fn cli() -> clap::Command { .conflicts_with("build") .action(clap::ArgAction::SetTrue) .group("section") - .help("Remove as development dependency"), + .help("Remove from dev-dependencies"), clap::Arg::new("build") .long("build") .conflicts_with("dev") .action(clap::ArgAction::SetTrue) .group("section") - .help("Remove as build dependency"), + .help("Remove from build-dependencies"), clap::Arg::new("target") .long("target") .num_args(1) .value_name("TARGET") .value_parser(clap::builder::NonEmptyStringValueParser::new()) - .help("Remove as dependency from the given target platform"), + .help("Remove from target-dependencies"), ]) .arg_package("Package to remove from") .arg_manifest_path() @@ -270,7 +270,10 @@ fn gc_workspace(workspace: &Workspace<'_>) -> CargoResult<()> { } if is_modified { - cargo_util::paths::write(workspace.root_manifest(), manifest.to_string().as_bytes())?; + cargo_util::paths::write_atomic( + workspace.root_manifest(), + manifest.to_string().as_bytes(), + )?; } Ok(()) @@ -283,7 +286,7 @@ fn spec_has_match( config: &Config, ) -> CargoResult<bool> { for dep in dependencies { - if spec.name().as_str() != &dep.name { + if spec.name() != &dep.name { continue; } diff --git a/src/tools/cargo/src/bin/cargo/commands/run.rs b/src/tools/cargo/src/bin/cargo/commands/run.rs index 029c9ee56..94396e63f 100644 --- a/src/tools/cargo/src/bin/cargo/commands/run.rs +++ b/src/tools/cargo/src/bin/cargo/commands/run.rs @@ -16,6 +16,7 @@ pub fn cli() -> Command { .about("Run a binary or example of the local package") .arg( Arg::new("args") + .value_name("ARGS") .help("Arguments for the binary or example to run") .value_parser(value_parser!(OsString)) .num_args(0..) diff --git a/src/tools/cargo/src/bin/cargo/commands/rustc.rs b/src/tools/cargo/src/bin/cargo/commands/rustc.rs index 60f0b9d60..9b6a57577 100644 --- a/src/tools/cargo/src/bin/cargo/commands/rustc.rs +++ b/src/tools/cargo/src/bin/cargo/commands/rustc.rs @@ -10,6 +10,7 @@ pub fn cli() -> Command { .about("Compile a package, and pass extra options to the compiler") .arg( Arg::new("args") + .value_name("ARGS") .num_args(0..) .help("Extra rustc flags") .trailing_var_arg(true), @@ -38,9 +39,9 @@ pub fn cli() -> Command { "Build only the specified example", "Build all examples", "Build only the specified test target", - "Build all tests", + "Build all test targets", "Build only the specified bench target", - "Build all benches", + "Build all bench targets", "Build all targets", ) .arg_features() diff --git a/src/tools/cargo/src/bin/cargo/commands/rustdoc.rs b/src/tools/cargo/src/bin/cargo/commands/rustdoc.rs index 8cb2f10de..72de57ad0 100644 --- a/src/tools/cargo/src/bin/cargo/commands/rustdoc.rs +++ b/src/tools/cargo/src/bin/cargo/commands/rustdoc.rs @@ -7,6 +7,7 @@ pub fn cli() -> Command { .about("Build a package's documentation, using specified custom flags.") .arg( Arg::new("args") + .value_name("ARGS") .help("Extra rustdoc flags") .num_args(0..) .trailing_var_arg(true), @@ -26,9 +27,9 @@ pub fn cli() -> Command { "Build only the specified example", "Build all examples", "Build only the specified test target", - "Build all tests", + "Build all test targets", "Build only the specified bench target", - "Build all benches", + "Build all bench targets", "Build all targets", ) .arg_features() diff --git a/src/tools/cargo/src/bin/cargo/commands/search.rs b/src/tools/cargo/src/bin/cargo/commands/search.rs index 9cacfc7e8..377aa84e1 100644 --- a/src/tools/cargo/src/bin/cargo/commands/search.rs +++ b/src/tools/cargo/src/bin/cargo/commands/search.rs @@ -7,7 +7,7 @@ use cargo::ops; pub fn cli() -> Command { subcommand("search") .about("Search packages in crates.io") - .arg(Arg::new("query").num_args(0..)) + .arg(Arg::new("query").value_name("QUERY").num_args(0..)) .arg( opt( "limit", diff --git a/src/tools/cargo/src/bin/cargo/commands/test.rs b/src/tools/cargo/src/bin/cargo/commands/test.rs index 3c7af506d..6e8aff043 100644 --- a/src/tools/cargo/src/bin/cargo/commands/test.rs +++ b/src/tools/cargo/src/bin/cargo/commands/test.rs @@ -13,6 +13,7 @@ pub fn cli() -> Command { ) .arg( Arg::new("args") + .value_name("ARGS") .help("Arguments for the test binary") .num_args(0..) .last(true), @@ -42,9 +43,9 @@ pub fn cli() -> Command { "Test only the specified example", "Test all examples", "Test only the specified test target", - "Test all tests", + "Test all test targets", "Test only the specified bench target", - "Test all benches", + "Test all bench targets", "Test all targets (does not include doctests)", ) .arg_features() diff --git a/src/tools/cargo/src/bin/cargo/commands/uninstall.rs b/src/tools/cargo/src/bin/cargo/commands/uninstall.rs index 9585d290b..30833f292 100644 --- a/src/tools/cargo/src/bin/cargo/commands/uninstall.rs +++ b/src/tools/cargo/src/bin/cargo/commands/uninstall.rs @@ -5,7 +5,7 @@ use cargo::ops; pub fn cli() -> Command { subcommand("uninstall") .about("Remove a Rust binary") - .arg(Arg::new("spec").num_args(0..)) + .arg(Arg::new("spec").value_name("SPEC").num_args(0..)) .arg(opt("root", "Directory to uninstall packages from").value_name("DIR")) .arg_quiet() .arg_package_spec_simple("Package to uninstall") diff --git a/src/tools/cargo/src/bin/cargo/commands/yank.rs b/src/tools/cargo/src/bin/cargo/commands/yank.rs index 62d1821c3..75a1772ca 100644 --- a/src/tools/cargo/src/bin/cargo/commands/yank.rs +++ b/src/tools/cargo/src/bin/cargo/commands/yank.rs @@ -6,7 +6,7 @@ use cargo_credential::Secret; pub fn cli() -> Command { subcommand("yank") .about("Remove a pushed crate from the index") - .arg(Arg::new("crate").action(ArgAction::Set)) + .arg(Arg::new("crate").value_name("CRATE").action(ArgAction::Set)) .arg( opt("version", "The version to yank or un-yank") .alias("vers") diff --git a/src/tools/cargo/src/bin/cargo/main.rs b/src/tools/cargo/src/bin/cargo/main.rs index 16e4f24f3..245622b6c 100644 --- a/src/tools/cargo/src/bin/cargo/main.rs +++ b/src/tools/cargo/src/bin/cargo/main.rs @@ -4,7 +4,7 @@ use cargo::util::network::http::http_handle; use cargo::util::network::http::needs_custom_http_transport; -use cargo::util::toml::StringOrVec; +use cargo::util::toml::schema::StringOrVec; use cargo::util::CliError; use cargo::util::{self, closest_msg, command_prelude, CargoResult, CliResult, Config}; use cargo_util::{ProcessBuilder, ProcessError}; @@ -192,10 +192,9 @@ fn execute_external_subcommand(config: &Config, cmd: &str, args: &[&OsStr]) -> C let did_you_mean = closest_msg(cmd, suggestions.keys(), |c| c); anyhow::format_err!( - "no such command: `{}`{}\n\n\t\ - View all installed commands with `cargo --list`", - cmd, - did_you_mean + "no such command: `{cmd}`{did_you_mean}\n\n\t\ + View all installed commands with `cargo --list`\n\t\ + Find a package to install `{cmd}` with `cargo search cargo-{cmd}`", ) }; diff --git a/src/tools/cargo/src/cargo/core/compiler/context/mod.rs b/src/tools/cargo/src/cargo/core/compiler/context/mod.rs index 010fe2793..cfbfccb30 100644 --- a/src/tools/cargo/src/cargo/core/compiler/context/mod.rs +++ b/src/tools/cargo/src/cargo/core/compiler/context/mod.rs @@ -7,6 +7,7 @@ use std::sync::{Arc, Mutex}; use crate::core::compiler::compilation::{self, UnitOutput}; use crate::core::compiler::{self, artifact, Unit}; use crate::core::PackageId; +use crate::util::cache_lock::CacheLockMode; use crate::util::errors::CargoResult; use crate::util::profile; use anyhow::{bail, Context as _}; @@ -132,6 +133,13 @@ impl<'a, 'cfg> Context<'a, 'cfg> { /// /// [`ops::cargo_compile`]: ../../../ops/cargo_compile/index.html pub fn compile(mut self, exec: &Arc<dyn Executor>) -> CargoResult<Compilation<'cfg>> { + // A shared lock is held during the duration of the build since rustc + // needs to read from the `src` cache, and we don't want other + // commands modifying the `src` cache while it is running. + let _lock = self + .bcx + .config + .acquire_package_cache_lock(CacheLockMode::Shared)?; let mut queue = JobQueue::new(self.bcx); let mut plan = BuildPlan::new(); let build_plan = self.bcx.build_config.build_plan; diff --git a/src/tools/cargo/src/cargo/core/compiler/custom_build.rs b/src/tools/cargo/src/cargo/core/compiler/custom_build.rs index 3eeeaa0ee..c921986a8 100644 --- a/src/tools/cargo/src/cargo/core/compiler/custom_build.rs +++ b/src/tools/cargo/src/cargo/core/compiler/custom_build.rs @@ -307,6 +307,10 @@ fn build_work(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult<Job> { cmd.env("CARGO_MANIFEST_LINKS", links); } + if let Some(trim_paths) = unit.profile.trim_paths.as_ref() { + cmd.env("CARGO_TRIM_PATHS", trim_paths.to_string()); + } + // Be sure to pass along all enabled features for this package, this is the // last piece of statically known information that we have. for feat in &unit.features { @@ -353,6 +357,10 @@ fn build_work(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult<Job> { ); cmd.env_remove("RUSTFLAGS"); + if cx.bcx.ws.config().extra_verbose() { + cmd.display_env_vars(); + } + // Gather the set of native dependencies that this package has along with // some other variables to close over. // @@ -399,10 +407,7 @@ fn build_work(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult<Job> { paths::create_dir_all(&script_out_dir)?; let nightly_features_allowed = cx.bcx.config.nightly_features_allowed; - let extra_check_cfg = match cx.bcx.config.cli_unstable().check_cfg { - Some((_, _, _, output)) => output, - None => false, - }; + let extra_check_cfg = cx.bcx.config.cli_unstable().check_cfg; let targets: Vec<Target> = unit.pkg.targets().to_vec(); // Need a separate copy for the fresh closure. let targets_fresh = targets.clone(); @@ -802,7 +807,7 @@ impl BuildOutput { if extra_check_cfg { check_cfgs.push(value.to_string()); } else { - warnings.push(format!("cargo:{} requires -Zcheck-cfg=output flag", key)); + warnings.push(format!("cargo:{} requires -Zcheck-cfg flag", key)); } } "rustc-env" => { @@ -1122,10 +1127,7 @@ fn prev_build_output(cx: &mut Context<'_, '_>, unit: &Unit) -> (Option<BuildOutp &unit.pkg.to_string(), &prev_script_out_dir, &script_out_dir, - match cx.bcx.config.cli_unstable().check_cfg { - Some((_, _, _, output)) => output, - None => false, - }, + cx.bcx.config.cli_unstable().check_cfg, cx.bcx.config.nightly_features_allowed, unit.pkg.targets(), ) diff --git a/src/tools/cargo/src/cargo/core/compiler/future_incompat.rs b/src/tools/cargo/src/cargo/core/compiler/future_incompat.rs index 907a0f97d..af44940bd 100644 --- a/src/tools/cargo/src/cargo/core/compiler/future_incompat.rs +++ b/src/tools/cargo/src/cargo/core/compiler/future_incompat.rs @@ -37,6 +37,7 @@ use crate::core::compiler::BuildContext; use crate::core::{Dependency, PackageId, Workspace}; use crate::sources::source::QueryKind; use crate::sources::SourceConfigMap; +use crate::util::cache_lock::CacheLockMode; use crate::util::{iter_join, CargoResult}; use anyhow::{bail, format_err, Context}; use serde::{Deserialize, Serialize}; @@ -166,7 +167,7 @@ impl OnDiskReports { let on_disk = serde_json::to_vec(&self).unwrap(); if let Err(e) = ws .target_dir() - .open_rw( + .open_rw_exclusive_create( FUTURE_INCOMPAT_FILE, ws.config(), "Future incompatibility report", @@ -190,7 +191,7 @@ impl OnDiskReports { /// Loads the on-disk reports. pub fn load(ws: &Workspace<'_>) -> CargoResult<OnDiskReports> { - let report_file = match ws.target_dir().open_ro( + let report_file = match ws.target_dir().open_ro_shared( FUTURE_INCOMPAT_FILE, ws.config(), "Future incompatible report", @@ -297,7 +298,10 @@ fn render_report(per_package_reports: &[FutureIncompatReportPackage]) -> BTreeMa /// This is best-effort - if an error occurs, `None` will be returned. fn get_updates(ws: &Workspace<'_>, package_ids: &BTreeSet<PackageId>) -> Option<String> { // This in general ignores all errors since this is opportunistic. - let _lock = ws.config().acquire_package_cache_lock().ok()?; + let _lock = ws + .config() + .acquire_package_cache_lock(CacheLockMode::DownloadExclusive) + .ok()?; // Create a set of updated registry sources. let map = SourceConfigMap::new(ws.config()).ok()?; let mut package_ids: BTreeSet<_> = package_ids diff --git a/src/tools/cargo/src/cargo/core/compiler/job_queue/mod.rs b/src/tools/cargo/src/cargo/core/compiler/job_queue/mod.rs index 738c8c267..e39fe184d 100644 --- a/src/tools/cargo/src/cargo/core/compiler/job_queue/mod.rs +++ b/src/tools/cargo/src/cargo/core/compiler/job_queue/mod.rs @@ -952,7 +952,10 @@ impl<'cfg> DrainState<'cfg> { } for warning in output.warnings.iter() { - bcx.config.shell().warn(warning)?; + let warning_with_package = + format!("{}@{}: {}", unit.pkg.name(), unit.pkg.version(), warning); + + bcx.config.shell().warn(warning_with_package)?; } if msg.is_some() { diff --git a/src/tools/cargo/src/cargo/core/compiler/layout.rs b/src/tools/cargo/src/cargo/core/compiler/layout.rs index d92adffeb..57b65907c 100644 --- a/src/tools/cargo/src/cargo/core/compiler/layout.rs +++ b/src/tools/cargo/src/cargo/core/compiler/layout.rs @@ -166,7 +166,7 @@ impl Layout { // For now we don't do any more finer-grained locking on the artifact // directory, so just lock the entire thing for the duration of this // compile. - let lock = dest.open_rw(".cargo-lock", ws.config(), "build directory")?; + let lock = dest.open_rw_exclusive_create(".cargo-lock", ws.config(), "build directory")?; let root = root.into_path_unlocked(); let dest = dest.into_path_unlocked(); let deps = dest.join("deps"); diff --git a/src/tools/cargo/src/cargo/core/compiler/mod.rs b/src/tools/cargo/src/cargo/core/compiler/mod.rs index b0f15bd61..ab43e9979 100644 --- a/src/tools/cargo/src/cargo/core/compiler/mod.rs +++ b/src/tools/cargo/src/cargo/core/compiler/mod.rs @@ -93,7 +93,8 @@ use crate::core::{Feature, PackageId, Target, Verbosity}; use crate::util::errors::{CargoResult, VerboseError}; use crate::util::interning::InternedString; use crate::util::machine_message::{self, Message}; -use crate::util::toml::TomlDebugInfo; +use crate::util::toml::schema::TomlDebugInfo; +use crate::util::toml::schema::TomlTrimPaths; use crate::util::{add_path_args, internal, iter_join_onto, profile}; use cargo_util::{paths, ProcessBuilder, ProcessError}; use rustfix::diagnostics::Applicability; @@ -950,6 +951,7 @@ fn build_base_args(cx: &Context<'_, '_>, cmd: &mut ProcessBuilder, unit: &Unit) incremental, strip, rustflags: profile_rustflags, + trim_paths, .. } = unit.profile.clone(); let test = unit.mode.is_any_test(); @@ -1028,6 +1030,10 @@ fn build_base_args(cx: &Context<'_, '_>, cmd: &mut ProcessBuilder, unit: &Unit) } } + if let Some(trim_paths) = trim_paths { + trim_paths_args(cmd, cx, unit, &trim_paths)?; + } + cmd.args(unit.pkg.manifest().lint_rustflags()); cmd.args(&profile_rustflags); if let Some(args) = cx.bcx.extra_args_for(unit) { @@ -1162,44 +1168,105 @@ fn features_args(unit: &Unit) -> Vec<OsString> { args } +/// Generates the `--remap-path-scope` and `--remap-path-prefix` for [RFC 3127]. +/// See also unstable feature [`-Ztrim-paths`]. +/// +/// [RFC 3127]: https://rust-lang.github.io/rfcs/3127-trim-paths.html +/// [`-Ztrim-paths`]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#profile-trim-paths-option +fn trim_paths_args( + cmd: &mut ProcessBuilder, + cx: &Context<'_, '_>, + unit: &Unit, + trim_paths: &TomlTrimPaths, +) -> CargoResult<()> { + if trim_paths.is_none() { + return Ok(()); + } + + // feature gate was checked during mainfest/config parsing. + cmd.arg("-Zunstable-options"); + cmd.arg(format!("-Zremap-path-scope={trim_paths}")); + + let sysroot_remap = { + let sysroot = &cx.bcx.target_data.info(unit.kind).sysroot; + let mut remap = OsString::from("--remap-path-prefix="); + remap.push(sysroot); + remap.push("/lib/rustlib/src/rust"); // See also `detect_sysroot_src_path()`. + remap.push("="); + remap.push("/rustc/"); + // This remap logic aligns with rustc: + // <https://github.com/rust-lang/rust/blob/c2ef3516/src/bootstrap/src/lib.rs#L1113-L1116> + if let Some(commit_hash) = cx.bcx.rustc().commit_hash.as_ref() { + remap.push(commit_hash); + } else { + remap.push(cx.bcx.rustc().version.to_string()); + } + remap + }; + cmd.arg(sysroot_remap); + + let package_remap = { + let pkg_root = unit.pkg.root(); + let ws_root = cx.bcx.ws.root(); + let is_local = unit.pkg.package_id().source_id().is_path(); + let mut remap = OsString::from("--remap-path-prefix="); + // Remapped to path relative to workspace root: + // + // * path dependencies under workspace root directory + // + // Remapped to `<pkg>-<version>` + // + // * registry dependencies + // * git dependencies + // * path dependencies outside workspace root directory + if is_local && pkg_root.strip_prefix(ws_root).is_ok() { + remap.push(ws_root); + remap.push("="); // empty to remap to relative paths. + } else { + remap.push(pkg_root); + remap.push("="); + remap.push(unit.pkg.name()); + remap.push("-"); + remap.push(unit.pkg.version().to_string()); + } + remap + }; + cmd.arg(package_remap); + + Ok(()) +} + /// Generates the `--check-cfg` arguments for the `unit`. /// See unstable feature [`check-cfg`]. /// /// [`check-cfg`]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#check-cfg fn check_cfg_args(cx: &Context<'_, '_>, unit: &Unit) -> Vec<OsString> { - if let Some((features, well_known_names, well_known_values, _output)) = - cx.bcx.config.cli_unstable().check_cfg - { - let mut args = Vec::with_capacity(unit.pkg.summary().features().len() * 2 + 4); - args.push(OsString::from("-Zunstable-options")); - - if features { - // This generate something like this: - // - values(feature) - // - values(feature, "foo", "bar") - let mut arg = OsString::from("values(feature"); - for (&feat, _) in unit.pkg.summary().features() { - arg.push(", \""); - arg.push(&feat); - arg.push("\""); + if cx.bcx.config.cli_unstable().check_cfg { + // This generate something like this: + // - cfg(feature, values()) + // - cfg(feature, values("foo", "bar")) + // + // NOTE: Despite only explicitly specifying `feature`, well known names and values + // are implicitly enabled when one or more `--check-cfg` argument is passed. + + let gross_cap_estimation = unit.pkg.summary().features().len() * 7 + 25; + let mut arg_feature = OsString::with_capacity(gross_cap_estimation); + arg_feature.push("cfg(feature, values("); + for (i, feature) in unit.pkg.summary().features().keys().enumerate() { + if i != 0 { + arg_feature.push(", "); } - arg.push(")"); - - args.push(OsString::from("--check-cfg")); - args.push(arg); - } - - if well_known_names { - args.push(OsString::from("--check-cfg")); - args.push(OsString::from("names()")); - } - - if well_known_values { - args.push(OsString::from("--check-cfg")); - args.push(OsString::from("values()")); + arg_feature.push("\""); + arg_feature.push(feature); + arg_feature.push("\""); } + arg_feature.push("))"); - args + vec![ + OsString::from("-Zunstable-options"), + OsString::from("--check-cfg"), + arg_feature, + ] } else { Vec::new() } diff --git a/src/tools/cargo/src/cargo/core/compiler/timings.rs b/src/tools/cargo/src/cargo/core/compiler/timings.rs index 7c388bd10..98c36cfdc 100644 --- a/src/tools/cargo/src/cargo/core/compiler/timings.rs +++ b/src/tools/cargo/src/cargo/core/compiler/timings.rs @@ -339,18 +339,21 @@ impl<'cfg> Timings<'cfg> { include_str!("timings.js") )?; drop(f); - let msg = format!( - "report saved to {}", - std::env::current_dir() - .unwrap_or_default() - .join(&filename) - .display() - ); + let unstamped_filename = timings_path.join("cargo-timing.html"); paths::link_or_copy(&filename, &unstamped_filename)?; - self.config - .shell() - .status_with_color("Timing", msg, &style::NOTE)?; + + let mut shell = self.config.shell(); + let timing_path = std::env::current_dir().unwrap_or_default().join(&filename); + let link = shell.err_file_hyperlink(&timing_path); + let msg = format!( + "report saved to {}{}{}", + link.open(), + timing_path.display(), + link.close() + ); + shell.status_with_color("Timing", msg, &style::NOTE)?; + Ok(()) } @@ -380,14 +383,7 @@ impl<'cfg> Timings<'cfg> { .unwrap_or_else(|_| "n/a".into()); let rustc_info = render_rustc_info(bcx); let error_msg = match error { - Some(e) => format!( - r#"\ - <tr> - <td class="error-text">Error:</td><td>{}</td> - </tr> -"#, - e - ), + Some(e) => format!(r#"<tr><td class="error-text">Error:</td><td>{e}</td></tr>"#), None => "".to_string(), }; write!( diff --git a/src/tools/cargo/src/cargo/core/dependency.rs b/src/tools/cargo/src/cargo/core/dependency.rs index f00bb0590..fe102842a 100644 --- a/src/tools/cargo/src/cargo/core/dependency.rs +++ b/src/tools/cargo/src/cargo/core/dependency.rs @@ -352,9 +352,7 @@ impl Dependency { // Only update the `precise` of this source to preserve other // information about dependency's source which may not otherwise be // tested during equality/hashing. - me.source_id = me - .source_id - .with_precise(id.source_id().precise().map(|s| s.to_string())); + me.source_id = me.source_id.with_precise_from(id.source_id()); self } diff --git a/src/tools/cargo/src/cargo/core/features.rs b/src/tools/cargo/src/cargo/core/features.rs index 5faa2087e..72a267f04 100644 --- a/src/tools/cargo/src/cargo/core/features.rs +++ b/src/tools/cargo/src/cargo/core/features.rs @@ -195,7 +195,7 @@ pub const SEE_CHANNELS: &str = /// [`LATEST_STABLE`]: Edition::LATEST_STABLE /// [this example]: https://github.com/rust-lang/cargo/blob/3ebb5f15a940810f250b68821149387af583a79e/src/doc/src/reference/unstable.md?plain=1#L1238-L1264 /// [`is_stable`]: Edition::is_stable -/// [`TomlManifest::to_real_manifest`]: crate::util::toml::TomlManifest::to_real_manifest +/// [`TomlManifest::to_real_manifest`]: crate::util::toml::schema::TomlManifest::to_real_manifest /// [`features!`]: macro.features.html #[derive(Clone, Copy, Debug, Hash, PartialOrd, Ord, Eq, PartialEq, Serialize, Deserialize)] pub enum Edition { @@ -205,20 +205,22 @@ pub enum Edition { Edition2018, /// The 2021 edition Edition2021, + /// The 2024 edition + Edition2024, } impl Edition { /// The latest edition that is unstable. /// /// This is `None` if there is no next unstable edition. - pub const LATEST_UNSTABLE: Option<Edition> = None; + pub const LATEST_UNSTABLE: Option<Edition> = Some(Edition::Edition2024); /// The latest stable edition. pub const LATEST_STABLE: Edition = Edition::Edition2021; /// Possible values allowed for the `--edition` CLI flag. /// /// This requires a static value due to the way clap works, otherwise I /// would have built this dynamically. - pub const CLI_VALUES: [&'static str; 3] = ["2015", "2018", "2021"]; + pub const CLI_VALUES: [&'static str; 4] = ["2015", "2018", "2021", "2024"]; /// Returns the first version that a particular edition was released on /// stable. @@ -228,6 +230,7 @@ impl Edition { Edition2015 => None, Edition2018 => Some(semver::Version::new(1, 31, 0)), Edition2021 => Some(semver::Version::new(1, 56, 0)), + Edition2024 => None, } } @@ -238,6 +241,7 @@ impl Edition { Edition2015 => true, Edition2018 => true, Edition2021 => true, + Edition2024 => false, } } @@ -250,6 +254,7 @@ impl Edition { Edition2015 => None, Edition2018 => Some(Edition2015), Edition2021 => Some(Edition2018), + Edition2024 => Some(Edition2021), } } @@ -260,7 +265,8 @@ impl Edition { match self { Edition2015 => Edition2018, Edition2018 => Edition2021, - Edition2021 => Edition2021, + Edition2021 => Edition2024, + Edition2024 => Edition2024, } } @@ -286,6 +292,7 @@ impl Edition { Edition2015 => false, Edition2018 => true, Edition2021 => true, + Edition2024 => false, } } @@ -298,6 +305,7 @@ impl Edition { Edition2015 => false, Edition2018 => true, Edition2021 => false, + Edition2024 => false, } } @@ -316,6 +324,7 @@ impl fmt::Display for Edition { Edition::Edition2015 => f.write_str("2015"), Edition::Edition2018 => f.write_str("2018"), Edition::Edition2021 => f.write_str("2021"), + Edition::Edition2024 => f.write_str("2024"), } } } @@ -326,13 +335,14 @@ impl FromStr for Edition { "2015" => Ok(Edition::Edition2015), "2018" => Ok(Edition::Edition2018), "2021" => Ok(Edition::Edition2021), - s if s.parse().map_or(false, |y: u16| y > 2021 && y < 2050) => bail!( + "2024" => Ok(Edition::Edition2024), + s if s.parse().map_or(false, |y: u16| y > 2024 && y < 2050) => bail!( "this version of Cargo is older than the `{}` edition, \ - and only supports `2015`, `2018`, and `2021` editions.", + and only supports `2015`, `2018`, `2021`, and `2024` editions.", s ), s => bail!( - "supported edition values are `2015`, `2018`, or `2021`, \ + "supported edition values are `2015`, `2018`, `2021`, or `2024`, \ but `{}` is unknown", s ), @@ -483,6 +493,12 @@ features! { // Allow specifying rustflags directly in a profile (stable, workspace_inheritance, "1.64", "reference/unstable.html#workspace-inheritance"), + + // Support for 2024 edition. + (unstable, edition2024, "", "reference/unstable.html#edition-2024"), + + // Allow setting trim-paths in a profile to control the sanitisation of file paths in build outputs. + (unstable, trim_paths, "", "reference/unstable.html#profile-trim-paths-option"), } pub struct Feature { @@ -718,8 +734,7 @@ unstable_cli_options!( #[serde(deserialize_with = "deserialize_build_std")] build_std: Option<Vec<String>> = ("Enable Cargo to compile the standard library itself as part of a crate graph compilation"), build_std_features: Option<Vec<String>> = ("Configure features enabled for the standard library itself when building the standard library"), - #[serde(deserialize_with = "deserialize_check_cfg")] - check_cfg: Option<(/*features:*/ bool, /*well_known_names:*/ bool, /*well_known_values:*/ bool, /*output:*/ bool)> = ("Specify scope of compile-time checking of `cfg` names/values"), + check_cfg: bool = ("Enable compile-time checking of `cfg` names/values/features"), codegen_backend: bool = ("Enable the `codegen-backend` option in profiles in .cargo/config.toml file"), config_include: bool = ("Enable the `include` key in config files"), direct_minimal_versions: bool = ("Resolve minimal dependency versions instead of maximum (direct dependencies only)"), @@ -743,6 +758,7 @@ unstable_cli_options!( separate_nightlies: bool = (HIDDEN), skip_rustdoc_fingerprint: bool = (HIDDEN), target_applies_to_host: bool = ("Enable the `target-applies-to-host` key in the .cargo/config.toml file"), + trim_paths: bool = ("Enable the `trim-paths` option in profiles"), unstable_options: bool = ("Allow the usage of unstable options"), ); @@ -829,20 +845,6 @@ where )) } -fn deserialize_check_cfg<'de, D>( - deserializer: D, -) -> Result<Option<(bool, bool, bool, bool)>, D::Error> -where - D: serde::Deserializer<'de>, -{ - use serde::de::Error; - let Some(crates) = <Option<Vec<String>>>::deserialize(deserializer)? else { - return Ok(None); - }; - - 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. @@ -911,32 +913,6 @@ fn parse_gitoxide( Ok(Some(out)) } -fn parse_check_cfg( - it: impl Iterator<Item = impl AsRef<str>>, -) -> CargoResult<Option<(bool, bool, bool, bool)>> { - let mut features = false; - let mut well_known_names = false; - let mut well_known_values = false; - let mut output = false; - - for e in it { - match e.as_ref() { - "features" => features = true, - "names" => well_known_names = true, - "values" => well_known_values = true, - "output" => output = true, - _ => bail!("unstable check-cfg only takes `features`, `names`, `values` or `output` as valid inputs"), - } - } - - Ok(Some(( - features, - well_known_names, - well_known_values, - output, - ))) -} - impl CliUnstable { pub fn parse( &mut self, @@ -1094,7 +1070,7 @@ impl CliUnstable { } "build-std-features" => self.build_std_features = Some(parse_features(v)), "check-cfg" => { - self.check_cfg = v.map_or(Ok(None), |v| parse_check_cfg(v.split(',')))? + self.check_cfg = parse_empty(k, v)?; } "codegen-backend" => self.codegen_backend = parse_empty(k, v)?, "config-include" => self.config_include = parse_empty(k, v)?, @@ -1117,6 +1093,7 @@ impl CliUnstable { "no-index-update" => self.no_index_update = parse_empty(k, v)?, "panic-abort-tests" => self.panic_abort_tests = parse_empty(k, v)?, "profile-rustflags" => self.profile_rustflags = parse_empty(k, v)?, + "trim-paths" => self.trim_paths = parse_empty(k, v)?, "publish-timeout" => self.publish_timeout = parse_empty(k, v)?, "rustdoc-map" => self.rustdoc_map = parse_empty(k, v)?, "rustdoc-scrape-examples" => self.rustdoc_scrape_examples = parse_empty(k, v)?, @@ -1125,7 +1102,10 @@ impl CliUnstable { "script" => self.script = parse_empty(k, v)?, "target-applies-to-host" => self.target_applies_to_host = parse_empty(k, v)?, "unstable-options" => self.unstable_options = parse_empty(k, v)?, - _ => bail!("unknown `-Z` flag specified: {}", k), + _ => bail!("\ + unknown `-Z` flag specified: {k}\n\n\ + For available unstable features, see https://doc.rust-lang.org/nightly/cargo/reference/unstable.html\n\ + If you intended to use an unstable rustc feature, try setting `RUSTFLAGS=\"-Z{k}\"`"), } Ok(()) diff --git a/src/tools/cargo/src/cargo/core/manifest.rs b/src/tools/cargo/src/cargo/core/manifest.rs index 7886abec3..66af40c10 100644 --- a/src/tools/cargo/src/cargo/core/manifest.rs +++ b/src/tools/cargo/src/cargo/core/manifest.rs @@ -18,7 +18,7 @@ use crate::core::{Dependency, PackageId, PackageIdSpec, SourceId, Summary}; use crate::core::{Edition, Feature, Features, WorkspaceConfig}; use crate::util::errors::*; use crate::util::interning::InternedString; -use crate::util::toml::{TomlManifest, TomlProfiles}; +use crate::util::toml::schema::{TomlManifest, TomlProfiles}; use crate::util::{short_hash, Config, Filesystem, RustVersion}; pub enum EitherManifest { diff --git a/src/tools/cargo/src/cargo/core/mod.rs b/src/tools/cargo/src/cargo/core/mod.rs index 9b56564a7..2add52d5c 100644 --- a/src/tools/cargo/src/cargo/core/mod.rs +++ b/src/tools/cargo/src/cargo/core/mod.rs @@ -14,7 +14,7 @@ pub use self::workspace::{ find_workspace_root, resolve_relative_path, MaybePackage, Workspace, WorkspaceConfig, WorkspaceRootConfig, }; -pub use crate::util::toml::InheritableFields; +pub use crate::util::toml::schema::InheritableFields; pub mod compiler; pub mod dependency; diff --git a/src/tools/cargo/src/cargo/core/package.rs b/src/tools/cargo/src/cargo/core/package.rs index 76f6c405b..274798474 100644 --- a/src/tools/cargo/src/cargo/core/package.rs +++ b/src/tools/cargo/src/cargo/core/package.rs @@ -24,7 +24,7 @@ use crate::core::resolver::{HasDevUnits, Resolve}; use crate::core::{Dependency, Manifest, PackageId, SourceId, Target}; use crate::core::{Summary, Workspace}; use crate::sources::source::{MaybePackage, SourceMap}; -use crate::util::config::PackageCacheLock; +use crate::util::cache_lock::{CacheLock, CacheLockMode}; use crate::util::errors::{CargoResult, HttpNotSuccessful}; use crate::util::interning::InternedString; use crate::util::network::http::http_handle_and_timeout; @@ -367,7 +367,7 @@ pub struct Downloads<'a, 'cfg> { next_speed_check_bytes_threshold: Cell<u64>, /// Global filesystem lock to ensure only one Cargo is downloading at a /// time. - _lock: PackageCacheLock<'cfg>, + _lock: CacheLock<'cfg>, } struct Download<'cfg> { @@ -465,7 +465,9 @@ impl<'cfg> PackageSet<'cfg> { timeout, next_speed_check: Cell::new(Instant::now()), next_speed_check_bytes_threshold: Cell::new(0), - _lock: self.config.acquire_package_cache_lock()?, + _lock: self + .config + .acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?, }) } @@ -478,6 +480,9 @@ impl<'cfg> PackageSet<'cfg> { pub fn get_many(&self, ids: impl IntoIterator<Item = PackageId>) -> CargoResult<Vec<&Package>> { let mut pkgs = Vec::new(); + let _lock = self + .config + .acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?; let mut downloads = self.enable_download()?; for id in ids { pkgs.extend(downloads.start(id)?); diff --git a/src/tools/cargo/src/cargo/core/package_id.rs b/src/tools/cargo/src/cargo/core/package_id.rs index ca126172c..3e9c03a47 100644 --- a/src/tools/cargo/src/cargo/core/package_id.rs +++ b/src/tools/cargo/src/cargo/core/package_id.rs @@ -12,7 +12,7 @@ use serde::ser; use crate::core::SourceId; use crate::util::interning::InternedString; -use crate::util::{CargoResult, ToSemver}; +use crate::util::CargoResult; static PACKAGE_ID_CACHE: OnceLock<Mutex<HashSet<&'static PackageIdInner>>> = OnceLock::new(); @@ -29,8 +29,7 @@ struct PackageIdInner { source_id: SourceId, } -// Custom equality that uses full equality of SourceId, rather than its custom equality, -// and Version, which usually ignores `build` metadata. +// Custom equality that uses full equality of SourceId, rather than its custom equality. // // The `build` part of the version is usually ignored (like a "comment"). // However, there are some cases where it is important. The download path from @@ -40,11 +39,7 @@ struct PackageIdInner { impl PartialEq for PackageIdInner { fn eq(&self, other: &Self) -> bool { self.name == other.name - && self.version.major == other.version.major - && self.version.minor == other.version.minor - && self.version.patch == other.version.patch - && self.version.pre == other.version.pre - && self.version.build == other.version.build + && self.version == other.version && self.source_id.full_eq(other.source_id) } } @@ -53,11 +48,7 @@ impl PartialEq for PackageIdInner { impl Hash for PackageIdInner { fn hash<S: hash::Hasher>(&self, into: &mut S) { self.name.hash(into); - self.version.major.hash(into); - self.version.minor.hash(into); - self.version.patch.hash(into); - self.version.pre.hash(into); - self.version.build.hash(into); + self.version.hash(into); self.source_id.full_hash(into); } } @@ -82,27 +73,31 @@ impl<'de> de::Deserialize<'de> for PackageId { D: de::Deserializer<'de>, { let string = String::deserialize(d)?; - let mut s = string.splitn(3, ' '); - let name = s.next().unwrap(); - let name = InternedString::new(name); - let Some(version) = s.next() else { - return Err(de::Error::custom("invalid serialized PackageId")); - }; - let version = version.to_semver().map_err(de::Error::custom)?; - let Some(url) = s.next() else { - return Err(de::Error::custom("invalid serialized PackageId")); - }; - let url = if url.starts_with('(') && url.ends_with(')') { - &url[1..url.len() - 1] - } else { - return Err(de::Error::custom("invalid serialized PackageId")); - }; + + let (field, rest) = string + .split_once(' ') + .ok_or_else(|| de::Error::custom("invalid serialized PackageId"))?; + let name = InternedString::new(field); + + let (field, rest) = rest + .split_once(' ') + .ok_or_else(|| de::Error::custom("invalid serialized PackageId"))?; + let version = field.parse().map_err(de::Error::custom)?; + + let url = + strip_parens(rest).ok_or_else(|| de::Error::custom("invalid serialized PackageId"))?; let source_id = SourceId::from_url(url).map_err(de::Error::custom)?; Ok(PackageId::pure(name, version, source_id)) } } +fn strip_parens(value: &str) -> Option<&str> { + let value = value.strip_prefix('(')?; + let value = value.strip_suffix(')')?; + Some(value) +} + impl PartialEq for PackageId { fn eq(&self, other: &PackageId) -> bool { if ptr::eq(self.inner, other.inner) { @@ -128,12 +123,12 @@ impl Hash for PackageId { } impl PackageId { - pub fn new<T: ToSemver>( + pub fn new( name: impl Into<InternedString>, - version: T, + version: &str, sid: SourceId, ) -> CargoResult<PackageId> { - let v = version.to_semver()?; + let v = version.parse()?; Ok(PackageId::pure(name.into(), v, sid)) } @@ -165,14 +160,6 @@ impl PackageId { self.inner.source_id } - pub fn with_precise(self, precise: Option<String>) -> PackageId { - PackageId::pure( - self.inner.name, - self.inner.version.clone(), - self.inner.source_id.with_precise(precise), - ) - } - pub fn with_source_id(self, source: SourceId) -> PackageId { PackageId::pure(self.inner.name, self.inner.version.clone(), source) } @@ -233,6 +220,16 @@ impl fmt::Debug for PackageId { } } +impl fmt::Debug for PackageIdInner { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("PackageIdInner") + .field("name", &self.name) + .field("version", &self.version.to_string()) + .field("source", &self.source_id.to_string()) + .finish() + } +} + #[cfg(test)] mod tests { use super::PackageId; @@ -257,4 +254,14 @@ mod tests { let pkg_id = PackageId::new("foo", "1.0.0", SourceId::for_registry(&loc).unwrap()).unwrap(); assert_eq!("foo v1.0.0", pkg_id.to_string()); } + + #[test] + fn unequal_build_metadata() { + let loc = CRATES_IO_INDEX.into_url().unwrap(); + let repo = SourceId::for_registry(&loc).unwrap(); + let first = PackageId::new("foo", "0.0.1+first", repo).unwrap(); + let second = PackageId::new("foo", "0.0.1+second", repo).unwrap(); + assert_ne!(first, second); + assert_ne!(first.inner, second.inner); + } } diff --git a/src/tools/cargo/src/cargo/core/package_id_spec.rs b/src/tools/cargo/src/cargo/core/package_id_spec.rs index 53d99b84b..c617c1f7a 100644 --- a/src/tools/cargo/src/cargo/core/package_id_spec.rs +++ b/src/tools/cargo/src/cargo/core/package_id_spec.rs @@ -9,9 +9,8 @@ use url::Url; use crate::core::PackageId; use crate::util::edit_distance; use crate::util::errors::CargoResult; -use crate::util::interning::InternedString; -use crate::util::PartialVersion; use crate::util::{validate_package_name, IntoUrl}; +use crate::util_semver::PartialVersion; /// Some or all of the data required to identify a package: /// @@ -24,7 +23,7 @@ use crate::util::{validate_package_name, IntoUrl}; /// sufficient to uniquely define a package ID. #[derive(Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)] pub struct PackageIdSpec { - name: InternedString, + name: String, version: Option<PartialVersion>, url: Option<Url>, } @@ -76,7 +75,7 @@ impl PackageIdSpec { }; validate_package_name(name, "pkgid", "")?; Ok(PackageIdSpec { - name: InternedString::new(name), + name: String::from(name), version, url: None, }) @@ -99,7 +98,7 @@ impl PackageIdSpec { /// fields filled in. pub fn from_package_id(package_id: PackageId) -> PackageIdSpec { PackageIdSpec { - name: package_id.name(), + name: String::from(package_id.name().as_str()), version: Some(package_id.version().clone().into()), url: Some(package_id.source_id().url().clone()), } @@ -127,18 +126,18 @@ impl PackageIdSpec { Some(fragment) => match fragment.split_once([':', '@']) { Some((name, part)) => { let version = part.parse::<PartialVersion>()?; - (InternedString::new(name), Some(version)) + (String::from(name), Some(version)) } None => { if fragment.chars().next().unwrap().is_alphabetic() { - (InternedString::new(&fragment), None) + (String::from(fragment.as_str()), None) } else { let version = fragment.parse::<PartialVersion>()?; - (InternedString::new(path_name), Some(version)) + (String::from(path_name), Some(version)) } } }, - None => (InternedString::new(path_name), None), + None => (String::from(path_name), None), } }; Ok(PackageIdSpec { @@ -148,13 +147,13 @@ impl PackageIdSpec { }) } - pub fn name(&self) -> InternedString { - self.name + pub fn name(&self) -> &str { + self.name.as_str() } /// Full `semver::Version`, if present pub fn version(&self) -> Option<Version> { - self.version.as_ref().and_then(|v| v.version()) + self.version.as_ref().and_then(|v| v.to_version()) } pub fn partial_version(&self) -> Option<&PartialVersion> { @@ -171,7 +170,7 @@ impl PackageIdSpec { /// Checks whether the given `PackageId` matches the `PackageIdSpec`. pub fn matches(&self, package_id: PackageId) -> bool { - if self.name() != package_id.name() { + if self.name() != package_id.name().as_str() { return false; } @@ -181,10 +180,13 @@ impl PackageIdSpec { } } - match self.url { - Some(ref u) => u == package_id.source_id().url(), - None => true, + if let Some(u) = &self.url { + if u != package_id.source_id().url() { + return false; + } } + + true } /// Checks a list of `PackageId`s to find 1 that matches this `PackageIdSpec`. If 0, 2, or @@ -211,7 +213,7 @@ impl PackageIdSpec { if self.url.is_some() { try_spec( PackageIdSpec { - name: self.name, + name: self.name.clone(), version: self.version.clone(), url: None, }, @@ -221,7 +223,7 @@ impl PackageIdSpec { if suggestion.is_empty() && self.version.is_some() { try_spec( PackageIdSpec { - name: self.name, + name: self.name.clone(), version: None, url: None, }, @@ -324,7 +326,6 @@ impl<'de> de::Deserialize<'de> for PackageIdSpec { mod tests { use super::PackageIdSpec; use crate::core::{PackageId, SourceId}; - use crate::util::interning::InternedString; use url::Url; #[test] @@ -333,13 +334,16 @@ mod tests { fn ok(spec: &str, expected: PackageIdSpec, expected_rendered: &str) { let parsed = PackageIdSpec::parse(spec).unwrap(); assert_eq!(parsed, expected); - assert_eq!(parsed.to_string(), expected_rendered); + let rendered = parsed.to_string(); + assert_eq!(rendered, expected_rendered); + let reparsed = PackageIdSpec::parse(&rendered).unwrap(); + assert_eq!(reparsed, expected); } ok( "https://crates.io/foo", PackageIdSpec { - name: InternedString::new("foo"), + name: String::from("foo"), version: None, url: Some(Url::parse("https://crates.io/foo").unwrap()), }, @@ -348,7 +352,7 @@ mod tests { ok( "https://crates.io/foo#1.2.3", PackageIdSpec { - name: InternedString::new("foo"), + name: String::from("foo"), version: Some("1.2.3".parse().unwrap()), url: Some(Url::parse("https://crates.io/foo").unwrap()), }, @@ -357,7 +361,7 @@ mod tests { ok( "https://crates.io/foo#1.2", PackageIdSpec { - name: InternedString::new("foo"), + name: String::from("foo"), version: Some("1.2".parse().unwrap()), url: Some(Url::parse("https://crates.io/foo").unwrap()), }, @@ -366,7 +370,7 @@ mod tests { ok( "https://crates.io/foo#bar:1.2.3", PackageIdSpec { - name: InternedString::new("bar"), + name: String::from("bar"), version: Some("1.2.3".parse().unwrap()), url: Some(Url::parse("https://crates.io/foo").unwrap()), }, @@ -375,7 +379,7 @@ mod tests { ok( "https://crates.io/foo#bar@1.2.3", PackageIdSpec { - name: InternedString::new("bar"), + name: String::from("bar"), version: Some("1.2.3".parse().unwrap()), url: Some(Url::parse("https://crates.io/foo").unwrap()), }, @@ -384,7 +388,7 @@ mod tests { ok( "https://crates.io/foo#bar@1.2", PackageIdSpec { - name: InternedString::new("bar"), + name: String::from("bar"), version: Some("1.2".parse().unwrap()), url: Some(Url::parse("https://crates.io/foo").unwrap()), }, @@ -393,7 +397,7 @@ mod tests { ok( "foo", PackageIdSpec { - name: InternedString::new("foo"), + name: String::from("foo"), version: None, url: None, }, @@ -402,7 +406,7 @@ mod tests { ok( "foo:1.2.3", PackageIdSpec { - name: InternedString::new("foo"), + name: String::from("foo"), version: Some("1.2.3".parse().unwrap()), url: None, }, @@ -411,7 +415,7 @@ mod tests { ok( "foo@1.2.3", PackageIdSpec { - name: InternedString::new("foo"), + name: String::from("foo"), version: Some("1.2.3".parse().unwrap()), url: None, }, @@ -420,12 +424,104 @@ mod tests { ok( "foo@1.2", PackageIdSpec { - name: InternedString::new("foo"), + name: String::from("foo"), version: Some("1.2".parse().unwrap()), url: None, }, "foo@1.2", ); + + // pkgid-spec.md + ok( + "regex", + PackageIdSpec { + name: String::from("regex"), + version: None, + url: None, + }, + "regex", + ); + ok( + "regex@1.4", + PackageIdSpec { + name: String::from("regex"), + version: Some("1.4".parse().unwrap()), + url: None, + }, + "regex@1.4", + ); + ok( + "regex@1.4.3", + PackageIdSpec { + name: String::from("regex"), + version: Some("1.4.3".parse().unwrap()), + url: None, + }, + "regex@1.4.3", + ); + ok( + "https://github.com/rust-lang/crates.io-index#regex", + PackageIdSpec { + name: String::from("regex"), + version: None, + url: Some(Url::parse("https://github.com/rust-lang/crates.io-index").unwrap()), + }, + "https://github.com/rust-lang/crates.io-index#regex", + ); + ok( + "https://github.com/rust-lang/crates.io-index#regex@1.4.3", + PackageIdSpec { + name: String::from("regex"), + version: Some("1.4.3".parse().unwrap()), + url: Some(Url::parse("https://github.com/rust-lang/crates.io-index").unwrap()), + }, + "https://github.com/rust-lang/crates.io-index#regex@1.4.3", + ); + ok( + "https://github.com/rust-lang/cargo#0.52.0", + PackageIdSpec { + name: String::from("cargo"), + version: Some("0.52.0".parse().unwrap()), + url: Some(Url::parse("https://github.com/rust-lang/cargo").unwrap()), + }, + "https://github.com/rust-lang/cargo#0.52.0", + ); + ok( + "https://github.com/rust-lang/cargo#cargo-platform@0.1.2", + PackageIdSpec { + name: String::from("cargo-platform"), + version: Some("0.1.2".parse().unwrap()), + url: Some(Url::parse("https://github.com/rust-lang/cargo").unwrap()), + }, + "https://github.com/rust-lang/cargo#cargo-platform@0.1.2", + ); + ok( + "ssh://git@github.com/rust-lang/regex.git#regex@1.4.3", + PackageIdSpec { + name: String::from("regex"), + version: Some("1.4.3".parse().unwrap()), + url: Some(Url::parse("ssh://git@github.com/rust-lang/regex.git").unwrap()), + }, + "ssh://git@github.com/rust-lang/regex.git#regex@1.4.3", + ); + ok( + "file:///path/to/my/project/foo", + PackageIdSpec { + name: String::from("foo"), + version: None, + url: Some(Url::parse("file:///path/to/my/project/foo").unwrap()), + }, + "file:///path/to/my/project/foo", + ); + ok( + "file:///path/to/my/project/foo#1.1.8", + PackageIdSpec { + name: String::from("foo"), + version: Some("1.1.8".parse().unwrap()), + url: Some(Url::parse("file:///path/to/my/project/foo").unwrap()), + }, + "file:///path/to/my/project/foo#1.1.8", + ); } #[test] @@ -452,6 +548,12 @@ mod tests { assert!(PackageIdSpec::parse("foo@1.2.3").unwrap().matches(foo)); assert!(!PackageIdSpec::parse("foo@1.2.2").unwrap().matches(foo)); assert!(PackageIdSpec::parse("foo@1.2").unwrap().matches(foo)); + assert!(PackageIdSpec::parse("https://example.com#foo@1.2") + .unwrap() + .matches(foo)); + assert!(!PackageIdSpec::parse("https://bob.com#foo@1.2") + .unwrap() + .matches(foo)); let meta = PackageId::new("meta", "1.2.3+hello", sid).unwrap(); assert!(PackageIdSpec::parse("meta").unwrap().matches(meta)); diff --git a/src/tools/cargo/src/cargo/core/profiles.rs b/src/tools/cargo/src/cargo/core/profiles.rs index 1ad9ed5f7..ec53dbae5 100644 --- a/src/tools/cargo/src/cargo/core/profiles.rs +++ b/src/tools/cargo/src/cargo/core/profiles.rs @@ -24,9 +24,12 @@ use crate::core::compiler::{CompileKind, CompileTarget, Unit}; use crate::core::dependency::Artifact; use crate::core::resolver::features::FeaturesFor; +use crate::core::Feature; use crate::core::{PackageId, PackageIdSpec, Resolve, Shell, Target, Workspace}; use crate::util::interning::InternedString; -use crate::util::toml::{ +use crate::util::toml::schema::TomlTrimPaths; +use crate::util::toml::schema::TomlTrimPathsValue; +use crate::util::toml::schema::{ ProfilePackageSpec, StringOrBool, TomlDebugInfo, TomlProfile, TomlProfiles, }; use crate::util::{closest_msg, config, CargoResult, Config}; @@ -80,7 +83,9 @@ impl Profiles { rustc_host, }; - Self::add_root_profiles(&mut profile_makers, &profiles); + let trim_paths_enabled = ws.unstable_features().is_enabled(Feature::trim_paths()) + || config.cli_unstable().trim_paths; + Self::add_root_profiles(&mut profile_makers, &profiles, trim_paths_enabled); // Merge with predefined profiles. use std::collections::btree_map::Entry; @@ -104,7 +109,7 @@ impl Profiles { // Verify that the requested profile is defined *somewhere*. // This simplifies the API (no need for CargoResult), and enforces // assumptions about how config profiles are loaded. - profile_makers.get_profile_maker(requested_profile)?; + profile_makers.get_profile_maker(&requested_profile)?; Ok(profile_makers) } @@ -123,6 +128,7 @@ impl Profiles { fn add_root_profiles( profile_makers: &mut Profiles, profiles: &BTreeMap<InternedString, TomlProfile>, + trim_paths_enabled: bool, ) { profile_makers.by_name.insert( InternedString::new("dev"), @@ -131,7 +137,10 @@ impl Profiles { profile_makers.by_name.insert( InternedString::new("release"), - ProfileMaker::new(Profile::default_release(), profiles.get("release").cloned()), + ProfileMaker::new( + Profile::default_release(trim_paths_enabled), + profiles.get("release").cloned(), + ), ); } @@ -142,21 +151,21 @@ impl Profiles { ( "bench", TomlProfile { - inherits: Some(InternedString::new("release")), + inherits: Some(String::from("release")), ..TomlProfile::default() }, ), ( "test", TomlProfile { - inherits: Some(InternedString::new("dev")), + inherits: Some(String::from("dev")), ..TomlProfile::default() }, ), ( "doc", TomlProfile { - inherits: Some(InternedString::new("dev")), + inherits: Some(String::from("dev")), ..TomlProfile::default() }, ), @@ -173,7 +182,7 @@ impl Profiles { match &profile.dir_name { None => {} Some(dir_name) => { - self.dir_names.insert(name, dir_name.to_owned()); + self.dir_names.insert(name, InternedString::new(dir_name)); } } @@ -212,12 +221,13 @@ impl Profiles { set: &mut HashSet<InternedString>, profiles: &BTreeMap<InternedString, TomlProfile>, ) -> CargoResult<ProfileMaker> { - let mut maker = match profile.inherits { + let mut maker = match &profile.inherits { Some(inherits_name) if inherits_name == "dev" || inherits_name == "release" => { // These are the root profiles added in `add_root_profiles`. - self.get_profile_maker(inherits_name).unwrap().clone() + self.get_profile_maker(&inherits_name).unwrap().clone() } Some(inherits_name) => { + let inherits_name = InternedString::new(&inherits_name); if !set.insert(inherits_name) { bail!( "profile inheritance loop detected with profile `{}` inheriting `{}`", @@ -263,7 +273,7 @@ impl Profiles { unit_for: UnitFor, kind: CompileKind, ) -> Profile { - let maker = self.get_profile_maker(self.requested_profile).unwrap(); + let maker = self.get_profile_maker(&self.requested_profile).unwrap(); let mut profile = maker.get_profile(Some(pkg_id), is_member, unit_for.is_for_host()); // Dealing with `panic=abort` and `panic=unwind` requires some special @@ -317,6 +327,7 @@ impl Profiles { result.root = for_unit_profile.root; result.debuginfo = for_unit_profile.debuginfo; result.opt_level = for_unit_profile.opt_level; + result.trim_paths = for_unit_profile.trim_paths.clone(); result } @@ -325,7 +336,7 @@ impl Profiles { /// select for the package that was actually built. pub fn base_profile(&self) -> Profile { let profile_name = self.requested_profile; - let maker = self.get_profile_maker(profile_name).unwrap(); + let maker = self.get_profile_maker(&profile_name).unwrap(); maker.get_profile(None, /*is_member*/ true, /*is_for_host*/ false) } @@ -372,9 +383,9 @@ impl Profiles { } /// Returns the profile maker for the given profile name. - fn get_profile_maker(&self, name: InternedString) -> CargoResult<&ProfileMaker> { + fn get_profile_maker(&self, name: &str) -> CargoResult<&ProfileMaker> { self.by_name - .get(&name) + .get(name) .ok_or_else(|| anyhow::format_err!("profile `{}` is not defined", name)) } } @@ -521,7 +532,7 @@ fn merge_profile(profile: &mut Profile, toml: &TomlProfile) { None => {} } if toml.codegen_backend.is_some() { - profile.codegen_backend = toml.codegen_backend; + profile.codegen_backend = toml.codegen_backend.as_ref().map(InternedString::from); } if toml.codegen_units.is_some() { profile.codegen_units = toml.codegen_units; @@ -553,7 +564,10 @@ fn merge_profile(profile: &mut Profile, toml: &TomlProfile) { profile.incremental = incremental; } if let Some(flags) = &toml.rustflags { - profile.rustflags = flags.clone(); + profile.rustflags = flags.iter().map(InternedString::from).collect(); + } + if let Some(trim_paths) = &toml.trim_paths { + profile.trim_paths = Some(trim_paths.clone()); } profile.strip = match toml.strip { Some(StringOrBool::Bool(true)) => Strip::Named(InternedString::new("symbols")), @@ -598,6 +612,9 @@ pub struct Profile { #[serde(skip_serializing_if = "Vec::is_empty")] // remove when `rustflags` is stablized // Note that `rustflags` is used for the cargo-feature `profile_rustflags` pub rustflags: Vec<InternedString>, + // remove when `-Ztrim-paths` is stablized + #[serde(skip_serializing_if = "Option::is_none")] + pub trim_paths: Option<TomlTrimPaths>, } impl Default for Profile { @@ -618,6 +635,7 @@ impl Default for Profile { panic: PanicStrategy::Unwind, strip: Strip::None, rustflags: vec![], + trim_paths: None, } } } @@ -627,7 +645,7 @@ compact_debug! { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let (default, default_name) = match self.name.as_str() { "dev" => (Profile::default_dev(), "default_dev()"), - "release" => (Profile::default_release(), "default_release()"), + "release" => (Profile::default_release(false), "default_release()"), _ => (Profile::default(), "default()"), }; [debug_the_fields( @@ -646,6 +664,7 @@ compact_debug! { panic strip rustflags + trim_paths )] } } @@ -687,11 +706,13 @@ impl Profile { } /// Returns a built-in `release` profile. - fn default_release() -> Profile { + fn default_release(trim_paths_enabled: bool) -> Profile { + let trim_paths = trim_paths_enabled.then(|| TomlTrimPathsValue::Object.into()); Profile { name: InternedString::new("release"), root: ProfileRoot::Release, opt_level: InternedString::new("3"), + trim_paths, ..Profile::default() } } @@ -712,6 +733,7 @@ impl Profile { self.rpath, (self.incremental, self.panic, self.strip), &self.rustflags, + &self.trim_paths, ) } } @@ -1162,7 +1184,11 @@ fn merge_config_profiles( requested_profile: InternedString, ) -> CargoResult<BTreeMap<InternedString, TomlProfile>> { let mut profiles = match ws.profiles() { - Some(profiles) => profiles.get_all().clone(), + Some(profiles) => profiles + .get_all() + .iter() + .map(|(k, v)| (InternedString::new(k), v.clone())) + .collect(), None => BTreeMap::new(), }; // Set of profile names to check if defined in config only. @@ -1174,7 +1200,7 @@ fn merge_config_profiles( profile.merge(&config_profile); } if let Some(inherits) = &profile.inherits { - check_to_add.insert(*inherits); + check_to_add.insert(InternedString::new(inherits)); } } // Add the built-in profiles. This is important for things like `cargo @@ -1188,10 +1214,10 @@ fn merge_config_profiles( while !check_to_add.is_empty() { std::mem::swap(&mut current, &mut check_to_add); for name in current.drain() { - if !profiles.contains_key(&name) { + if !profiles.contains_key(name.as_str()) { if let Some(config_profile) = get_config_profile(ws, &name)? { if let Some(inherits) = &config_profile.inherits { - check_to_add.insert(*inherits); + check_to_add.insert(InternedString::new(inherits)); } profiles.insert(name, config_profile); } diff --git a/src/tools/cargo/src/cargo/core/registry.rs b/src/tools/cargo/src/cargo/core/registry.rs index 9a6a5a035..a91f2986a 100644 --- a/src/tools/cargo/src/cargo/core/registry.rs +++ b/src/tools/cargo/src/cargo/core/registry.rs @@ -116,7 +116,7 @@ enum Kind { /// directive that we found in a lockfile, if present. pub struct LockedPatchDependency { /// The original `Dependency` directive, except "locked" so it's version - /// requirement is `=foo` and its `SourceId` has a "precise" listed. + /// requirement is Locked to `foo` and its `SourceId` has a "precise" listed. pub dependency: Dependency, /// The `PackageId` that was previously found in a lock file which /// `dependency` matches. @@ -161,7 +161,7 @@ impl<'cfg> PackageRegistry<'cfg> { // If the previous source was not a precise source, then we can be // sure that it's already been updated if we've already loaded it. - Some((previous, _)) if previous.precise().is_none() => { + Some((previous, _)) if !previous.has_precise() => { debug!("load/precise {}", namespace); return Ok(()); } @@ -170,7 +170,7 @@ impl<'cfg> PackageRegistry<'cfg> { // then we're done, otherwise we need to need to move forward // updating this source. Some((previous, _)) => { - if previous.precise() == namespace.precise() { + if previous.has_same_precise_as(namespace) { debug!("load/match {}", namespace); return Ok(()); } @@ -471,9 +471,9 @@ impl<'cfg> PackageRegistry<'cfg> { // // If we have a precise version, then we'll update lazily during the // querying phase. Note that precise in this case is only - // `Some("locked")` as other `Some` values indicate a `cargo update + // `"locked"` as other values indicate a `cargo update // --precise` request - if source_id.precise() != Some("locked") { + if !source_id.has_locked_precise() { self.sources.get_mut(source_id).unwrap().invalidate_cache(); } else { debug!("skipping update due to locked registry"); diff --git a/src/tools/cargo/src/cargo/core/resolver/context.rs b/src/tools/cargo/src/cargo/core/resolver/context.rs index f19c678a6..09b16b39c 100644 --- a/src/tools/cargo/src/cargo/core/resolver/context.rs +++ b/src/tools/cargo/src/cargo/core/resolver/context.rs @@ -10,10 +10,6 @@ use std::collections::HashMap; use std::num::NonZeroU64; use tracing::debug; -pub use super::encode::Metadata; -pub use super::encode::{EncodableDependency, EncodablePackageId, EncodableResolve}; -pub use super::resolve::Resolve; - // A `Context` is basically a bunch of local resolution information which is // kept around for all `BacktrackFrame` instances. As a result, this runs the // risk of being cloned *a lot* so we want to make this as cheap to clone as diff --git a/src/tools/cargo/src/cargo/core/resolver/dep_cache.rs b/src/tools/cargo/src/cargo/core/resolver/dep_cache.rs index 9041c5b0f..6c904c148 100644 --- a/src/tools/cargo/src/cargo/core/resolver/dep_cache.rs +++ b/src/tools/cargo/src/cargo/core/resolver/dep_cache.rs @@ -20,7 +20,6 @@ use crate::core::{Dependency, FeatureValue, PackageId, PackageIdSpec, Registry, use crate::sources::source::QueryKind; use crate::util::errors::CargoResult; use crate::util::interning::InternedString; -use crate::util::RustVersion; use anyhow::Context as _; use std::collections::{BTreeSet, HashMap, HashSet}; @@ -32,16 +31,11 @@ pub struct RegistryQueryer<'a> { pub registry: &'a mut (dyn Registry + 'a), replacements: &'a [(PackageIdSpec, Dependency)], version_prefs: &'a VersionPreferences, - /// If set the list of dependency candidates will be sorted by minimal - /// versions first. That allows `cargo update -Z minimal-versions` which will - /// specify minimum dependency versions to be used. - minimal_versions: bool, - max_rust_version: Option<RustVersion>, - /// a cache of `Candidate`s that fulfil a `Dependency` (and whether `first_minimal_version`) - registry_cache: HashMap<(Dependency, bool), Poll<Rc<Vec<Summary>>>>, + /// a cache of `Candidate`s that fulfil a `Dependency` (and whether `first_version`) + registry_cache: HashMap<(Dependency, Option<VersionOrdering>), Poll<Rc<Vec<Summary>>>>, /// 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 + /// HACK: `first_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< @@ -57,15 +51,11 @@ impl<'a> RegistryQueryer<'a> { registry: &'a mut dyn Registry, replacements: &'a [(PackageIdSpec, Dependency)], version_prefs: &'a VersionPreferences, - minimal_versions: bool, - max_rust_version: Option<&RustVersion>, ) -> Self { RegistryQueryer { registry, replacements, version_prefs, - minimal_versions, - max_rust_version: max_rust_version.cloned(), registry_cache: HashMap::new(), summary_cache: HashMap::new(), used_replacements: HashMap::new(), @@ -106,23 +96,20 @@ impl<'a> RegistryQueryer<'a> { pub fn query( &mut self, dep: &Dependency, - first_minimal_version: bool, + first_version: Option<VersionOrdering>, ) -> Poll<CargoResult<Rc<Vec<Summary>>>> { - let registry_cache_key = (dep.clone(), first_minimal_version); + let registry_cache_key = (dep.clone(), first_version); if let Some(out) = self.registry_cache.get(®istry_cache_key).cloned() { return out.map(Result::Ok); } let mut ret = Vec::new(); let ready = self.registry.query(dep, QueryKind::Exact, &mut |s| { - if self.max_rust_version.is_none() || s.rust_version() <= self.max_rust_version.as_ref() - { - ret.push(s); - } + ret.push(s); })?; if ready.is_pending() { self.registry_cache - .insert((dep.clone(), first_minimal_version), Poll::Pending); + .insert((dep.clone(), first_version), Poll::Pending); return Poll::Pending; } for summary in ret.iter() { @@ -144,7 +131,7 @@ impl<'a> RegistryQueryer<'a> { Poll::Ready(s) => s.into_iter(), Poll::Pending => { self.registry_cache - .insert((dep.clone(), first_minimal_version), Poll::Pending); + .insert((dep.clone(), first_version), Poll::Pending); return Poll::Pending; } }; @@ -215,16 +202,8 @@ 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. - 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 first_version = first_version; + self.version_prefs.sort_summaries(&mut ret, first_version); let out = Poll::Ready(Rc::new(ret)); @@ -243,7 +222,7 @@ impl<'a> RegistryQueryer<'a> { parent: Option<PackageId>, candidate: &Summary, opts: &ResolveOpts, - first_minimal_version: bool, + first_version: Option<VersionOrdering>, ) -> ActivateResult<Rc<(HashSet<InternedString>, Rc<Vec<DepInfo>>)>> { // if we have calculated a result before, then we can just return it, // as it is a "pure" query of its arguments. @@ -263,24 +242,22 @@ impl<'a> RegistryQueryer<'a> { let mut all_ready = true; let mut deps = deps .into_iter() - .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()), - ) - })), - }, - ) + .filter_map(|(dep, features)| match self.query(&dep, first_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::<CargoResult<Vec<DepInfo>>>()?; // Attempt to resolve dependencies with fewer candidates before trying diff --git a/src/tools/cargo/src/cargo/core/resolver/encode.rs b/src/tools/cargo/src/cargo/core/resolver/encode.rs index 7835c2219..fcef1578a 100644 --- a/src/tools/cargo/src/cargo/core/resolver/encode.rs +++ b/src/tools/cargo/src/cargo/core/resolver/encode.rs @@ -776,7 +776,7 @@ pub fn encodable_package_id( } } } - let mut source = encodable_source_id(id_to_encode.with_precise(None), resolve_version); + let mut source = encodable_source_id(id_to_encode.without_precise(), resolve_version); if let Some(counts) = &state.counts { let version_counts = &counts[&id.name()]; if version_counts[&id.version()] == 1 { diff --git a/src/tools/cargo/src/cargo/core/resolver/errors.rs b/src/tools/cargo/src/cargo/core/resolver/errors.rs index b57a7c3eb..15a006ffb 100644 --- a/src/tools/cargo/src/cargo/core/resolver/errors.rs +++ b/src/tools/cargo/src/cargo/core/resolver/errors.rs @@ -4,7 +4,8 @@ use std::task::Poll; use crate::core::{Dependency, PackageId, Registry, Summary}; use crate::sources::source::QueryKind; use crate::util::edit_distance::edit_distance; -use crate::util::{Config, OptVersionReq, VersionExt}; +use crate::util::{Config, OptVersionReq}; +use crate::util_semver::VersionExt; use anyhow::Error; use super::context::Context; diff --git a/src/tools/cargo/src/cargo/core/resolver/mod.rs b/src/tools/cargo/src/cargo/core/resolver/mod.rs index 7d8e8acd4..ecb6f36e6 100644 --- a/src/tools/cargo/src/cargo/core/resolver/mod.rs +++ b/src/tools/cargo/src/cargo/core/resolver/mod.rs @@ -71,7 +71,6 @@ use crate::util::config::Config; use crate::util::errors::CargoResult; use crate::util::network::PollExt; use crate::util::profile; -use crate::util::RustVersion; use self::context::Context; use self::dep_cache::RegistryQueryer; @@ -139,39 +138,18 @@ pub fn resolve( version_prefs: &VersionPreferences, config: Option<&Config>, check_public_visible_dependencies: bool, - mut max_rust_version: Option<&RustVersion>, ) -> CargoResult<Resolve> { let _p = profile::start("resolving"); - let minimal_versions = match config { - 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 first_version = match config { + Some(config) if config.cli_unstable().direct_minimal_versions => { + Some(VersionOrdering::MinimumVersionsFirst) + } + _ => None, }; - if !config - .map(|c| c.cli_unstable().msrv_policy) - .unwrap_or(false) - { - max_rust_version = None; - } - let mut registry = RegistryQueryer::new( - registry, - replacements, - version_prefs, - minimal_versions, - max_rust_version, - ); + let mut registry = RegistryQueryer::new(registry, replacements, version_prefs); let cx = loop { let cx = Context::new(check_public_visible_dependencies); - let cx = activate_deps_loop( - cx, - &mut registry, - summaries, - direct_minimal_versions, - config, - )?; + let cx = activate_deps_loop(cx, &mut registry, summaries, first_version, config)?; if registry.reset_pending() { break cx; } else { @@ -223,7 +201,7 @@ fn activate_deps_loop( mut cx: Context, registry: &mut RegistryQueryer<'_>, summaries: &[(Summary, ResolveOpts)], - direct_minimal_versions: bool, + first_version: Option<VersionOrdering>, config: Option<&Config>, ) -> CargoResult<Context> { let mut backtrack_stack = Vec::new(); @@ -241,7 +219,7 @@ fn activate_deps_loop( registry, None, summary.clone(), - direct_minimal_versions, + first_version, opts, ); match res { @@ -441,13 +419,13 @@ fn activate_deps_loop( dep.package_name(), candidate.version() ); - let direct_minimal_version = false; // this is an indirect dependency + let first_version = None; // this is an indirect dependency let res = activate( &mut cx, registry, Some((&parent, &dep)), candidate, - direct_minimal_version, + first_version, &opts, ); @@ -659,7 +637,7 @@ fn activate( registry: &mut RegistryQueryer<'_>, parent: Option<(&Summary, &Dependency)>, candidate: Summary, - first_minimal_version: bool, + first_version: Option<VersionOrdering>, opts: &ResolveOpts, ) -> ActivateResult<Option<(DepsFrame, Duration)>> { let candidate_pid = candidate.package_id(); @@ -716,7 +694,7 @@ fn activate( parent.map(|p| p.0.package_id()), &candidate, opts, - first_minimal_version, + first_version, )?; // Record what list of features is active for this package. @@ -905,14 +883,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 + // We only want `first_version.is_some()` for direct dependencies of workspace // members which isn't the case here as this has a `parent` - let first_minimal_version = false; + let first_version = None; // 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, first_minimal_version) + .query(critical_parents_dep, first_version) .expect("an already used dep now error!?") .expect("an already used dep now pending!?") .iter() diff --git a/src/tools/cargo/src/cargo/core/resolver/resolve.rs b/src/tools/cargo/src/cargo/core/resolver/resolve.rs index 18a389773..b401e9232 100644 --- a/src/tools/cargo/src/cargo/core/resolver/resolve.rs +++ b/src/tools/cargo/src/cargo/core/resolver/resolve.rs @@ -88,6 +88,17 @@ pub enum ResolveVersion { V4, } +impl ResolveVersion { + /// The maximum version of lockfile made into the stable channel. + /// + /// Any version larger than this needs `-Znext-lockfile-bump` to enable. + /// + /// Update this when you're going to stabilize a new lockfile format. + pub fn max_stable() -> ResolveVersion { + ResolveVersion::V3 + } +} + impl Resolve { pub fn new( graph: Graph<PackageId, HashSet<Dependency>>, diff --git a/src/tools/cargo/src/cargo/core/resolver/version_prefs.rs b/src/tools/cargo/src/cargo/core/resolver/version_prefs.rs index 28de77f11..0deef5565 100644 --- a/src/tools/cargo/src/cargo/core/resolver/version_prefs.rs +++ b/src/tools/cargo/src/cargo/core/resolver/version_prefs.rs @@ -6,6 +6,7 @@ use std::collections::{HashMap, HashSet}; use crate::core::{Dependency, PackageId, Summary}; use crate::util::interning::InternedString; +use crate::util::RustVersion; /// A collection of preferences for particular package versions. /// @@ -18,9 +19,13 @@ use crate::util::interning::InternedString; pub struct VersionPreferences { try_to_use: HashSet<PackageId>, prefer_patch_deps: HashMap<InternedString, HashSet<Dependency>>, + version_ordering: VersionOrdering, + max_rust_version: Option<RustVersion>, } +#[derive(Copy, Clone, Default, PartialEq, Eq, Hash, Debug)] pub enum VersionOrdering { + #[default] MaximumVersionsFirst, MinimumVersionsFirst, } @@ -39,14 +44,29 @@ impl VersionPreferences { .insert(dep); } - /// 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 version_ordering(&mut self, ordering: VersionOrdering) { + self.version_ordering = ordering; + } + + pub fn max_rust_version(&mut self, ver: Option<RustVersion>) { + self.max_rust_version = ver; + } + + /// Sort (and filter) the given vector of summaries in-place + /// + /// Note: all summaries presumed to be for the same package. + /// + /// Sort order: + /// 1. Preferred packages + /// 2. `first_version`, falling back to [`VersionPreferences::version_ordering`] when `None` + /// + /// Filtering: + /// - [`VersionPreferences::max_rust_version`] + /// - `first_version` pub fn sort_summaries( &self, summaries: &mut Vec<Summary>, - version_ordering: VersionOrdering, - first_version: bool, + first_version: Option<VersionOrdering>, ) { let should_prefer = |pkg_id: &PackageId| { self.try_to_use.contains(pkg_id) @@ -56,22 +76,24 @@ impl VersionPreferences { .map(|deps| deps.iter().any(|d| d.matches_id(*pkg_id))) .unwrap_or(false) }; + if self.max_rust_version.is_some() { + summaries.retain(|s| s.rust_version() <= self.max_rust_version.as_ref()); + } summaries.sort_unstable_by(|a, b| { let prefer_a = should_prefer(&a.package_id()); let prefer_b = should_prefer(&b.package_id()); let previous_cmp = prefer_a.cmp(&prefer_b).reverse(); - match previous_cmp { - Ordering::Equal => { - let cmp = a.version().cmp(b.version()); - match version_ordering { - VersionOrdering::MaximumVersionsFirst => cmp.reverse(), - VersionOrdering::MinimumVersionsFirst => cmp, - } - } - _ => previous_cmp, + if previous_cmp != Ordering::Equal { + return previous_cmp; + } + + let cmp = a.version().cmp(b.version()); + match first_version.unwrap_or(self.version_ordering) { + VersionOrdering::MaximumVersionsFirst => cmp.reverse(), + VersionOrdering::MinimumVersionsFirst => cmp, } }); - if first_version { + if first_version.is_some() { let _ = summaries.split_off(1); } } @@ -81,7 +103,6 @@ impl VersionPreferences { mod test { use super::*; use crate::core::SourceId; - use crate::util::RustVersion; use std::collections::BTreeMap; fn pkgid(name: &str, version: &str) -> PackageId { @@ -96,7 +117,7 @@ mod test { Dependency::parse(name, Some(version), src_id).unwrap() } - fn summ(name: &str, version: &str) -> Summary { + fn summ(name: &str, version: &str, msrv: Option<&str>) -> Summary { let pkg_id = pkgid(name, version); let features = BTreeMap::new(); Summary::new( @@ -104,7 +125,7 @@ mod test { Vec::new(), &features, None::<&String>, - None::<RustVersion>, + msrv.map(|m| m.parse().unwrap()), ) .unwrap() } @@ -123,19 +144,21 @@ mod test { vp.prefer_package_id(pkgid("foo", "1.2.3")); let mut summaries = vec![ - summ("foo", "1.2.4"), - summ("foo", "1.2.3"), - summ("foo", "1.1.0"), - summ("foo", "1.0.9"), + summ("foo", "1.2.4", None), + summ("foo", "1.2.3", None), + summ("foo", "1.1.0", None), + summ("foo", "1.0.9", None), ]; - vp.sort_summaries(&mut summaries, VersionOrdering::MaximumVersionsFirst, false); + vp.version_ordering(VersionOrdering::MaximumVersionsFirst); + vp.sort_summaries(&mut summaries, None); 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, false); + vp.version_ordering(VersionOrdering::MinimumVersionsFirst); + vp.sort_summaries(&mut summaries, None); assert_eq!( describe(&summaries), "foo/1.2.3, foo/1.0.9, foo/1.1.0, foo/1.2.4".to_string() @@ -148,19 +171,21 @@ mod test { vp.prefer_dependency(dep("foo", "=1.2.3")); let mut summaries = vec![ - summ("foo", "1.2.4"), - summ("foo", "1.2.3"), - summ("foo", "1.1.0"), - summ("foo", "1.0.9"), + summ("foo", "1.2.4", None), + summ("foo", "1.2.3", None), + summ("foo", "1.1.0", None), + summ("foo", "1.0.9", None), ]; - vp.sort_summaries(&mut summaries, VersionOrdering::MaximumVersionsFirst, false); + vp.version_ordering(VersionOrdering::MaximumVersionsFirst); + vp.sort_summaries(&mut summaries, None); 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, false); + vp.version_ordering(VersionOrdering::MinimumVersionsFirst); + vp.sort_summaries(&mut summaries, None); assert_eq!( describe(&summaries), "foo/1.2.3, foo/1.0.9, foo/1.1.0, foo/1.2.4".to_string() @@ -174,22 +199,51 @@ mod test { vp.prefer_dependency(dep("foo", "=1.1.0")); let mut summaries = vec![ - summ("foo", "1.2.4"), - summ("foo", "1.2.3"), - summ("foo", "1.1.0"), - summ("foo", "1.0.9"), + summ("foo", "1.2.4", None), + summ("foo", "1.2.3", None), + summ("foo", "1.1.0", None), + summ("foo", "1.0.9", None), ]; - vp.sort_summaries(&mut summaries, VersionOrdering::MaximumVersionsFirst, false); + vp.version_ordering(VersionOrdering::MaximumVersionsFirst); + vp.sort_summaries(&mut summaries, None); 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, false); + vp.version_ordering(VersionOrdering::MinimumVersionsFirst); + vp.sort_summaries(&mut summaries, None); assert_eq!( describe(&summaries), "foo/1.1.0, foo/1.2.3, foo/1.0.9, foo/1.2.4".to_string() ); } + + #[test] + fn test_max_rust_version() { + let mut vp = VersionPreferences::default(); + vp.max_rust_version(Some("1.50".parse().unwrap())); + + let mut summaries = vec![ + summ("foo", "1.2.4", Some("1.60")), + summ("foo", "1.2.3", Some("1.50")), + summ("foo", "1.1.0", Some("1.40")), + summ("foo", "1.0.9", None), + ]; + + vp.version_ordering(VersionOrdering::MaximumVersionsFirst); + vp.sort_summaries(&mut summaries, None); + assert_eq!( + describe(&summaries), + "foo/1.2.3, foo/1.1.0, foo/1.0.9".to_string() + ); + + vp.version_ordering(VersionOrdering::MinimumVersionsFirst); + vp.sort_summaries(&mut summaries, None); + assert_eq!( + describe(&summaries), + "foo/1.0.9, foo/1.1.0, foo/1.2.3".to_string() + ); + } } diff --git a/src/tools/cargo/src/cargo/core/shell.rs b/src/tools/cargo/src/cargo/core/shell.rs index a9b9e84af..3d446664f 100644 --- a/src/tools/cargo/src/cargo/core/shell.rs +++ b/src/tools/cargo/src/cargo/core/shell.rs @@ -6,6 +6,7 @@ use anstream::AutoStream; use anstyle::Style; use crate::util::errors::CargoResult; +use crate::util::hostname; use crate::util::style::*; pub enum TtyWidth { @@ -57,6 +58,7 @@ pub struct Shell { /// Flag that indicates the current line needs to be cleared before /// printing. Used when a progress bar is currently displayed. needs_clear: bool, + hostname: Option<String>, } impl fmt::Debug for Shell { @@ -85,6 +87,7 @@ enum ShellOut { stderr: AutoStream<std::io::Stderr>, stderr_tty: bool, color_choice: ColorChoice, + hyperlinks: bool, }, } @@ -111,10 +114,12 @@ impl Shell { stdout: AutoStream::new(std::io::stdout(), stdout_choice), stderr: AutoStream::new(std::io::stderr(), stderr_choice), color_choice: auto_clr, + hyperlinks: supports_hyperlinks(), stderr_tty: std::io::stderr().is_terminal(), }, verbosity: Verbosity::Verbose, needs_clear: false, + hostname: None, } } @@ -124,6 +129,7 @@ impl Shell { output: ShellOut::Write(AutoStream::never(out)), // strip all formatting on write verbosity: Verbosity::Verbose, needs_clear: false, + hostname: None, } } @@ -314,6 +320,16 @@ impl Shell { Ok(()) } + pub fn set_hyperlinks(&mut self, yes: bool) -> CargoResult<()> { + if let ShellOut::Stream { + ref mut hyperlinks, .. + } = self.output + { + *hyperlinks = yes; + } + Ok(()) + } + /// Gets the current color choice. /// /// If we are not using a color stream, this will always return `Never`, even if the color @@ -340,18 +356,59 @@ impl Shell { } } - /// Write a styled fragment - /// - /// Caller is responsible for deciding whether [`Shell::verbosity`] is affects output. - pub fn write_stdout(&mut self, fragment: impl fmt::Display, color: &Style) -> CargoResult<()> { - self.output.write_stdout(fragment, color) + pub fn out_hyperlink<D: fmt::Display>(&self, url: D) -> Hyperlink<D> { + let supports_hyperlinks = match &self.output { + ShellOut::Write(_) => false, + ShellOut::Stream { + stdout, hyperlinks, .. + } => stdout.current_choice() == anstream::ColorChoice::AlwaysAnsi && *hyperlinks, + }; + Hyperlink { + url: supports_hyperlinks.then_some(url), + } } - /// Write a styled fragment - /// - /// Caller is responsible for deciding whether [`Shell::verbosity`] is affects output. - pub fn write_stderr(&mut self, fragment: impl fmt::Display, color: &Style) -> CargoResult<()> { - self.output.write_stderr(fragment, color) + pub fn err_hyperlink<D: fmt::Display>(&self, url: D) -> Hyperlink<D> { + let supports_hyperlinks = match &self.output { + ShellOut::Write(_) => false, + ShellOut::Stream { + stderr, hyperlinks, .. + } => stderr.current_choice() == anstream::ColorChoice::AlwaysAnsi && *hyperlinks, + }; + if supports_hyperlinks { + Hyperlink { url: Some(url) } + } else { + Hyperlink { url: None } + } + } + + pub fn out_file_hyperlink(&mut self, path: &std::path::Path) -> Hyperlink<url::Url> { + let url = self.file_hyperlink(path); + url.map(|u| self.out_hyperlink(u)).unwrap_or_default() + } + + pub fn err_file_hyperlink(&mut self, path: &std::path::Path) -> Hyperlink<url::Url> { + let url = self.file_hyperlink(path); + url.map(|u| self.err_hyperlink(u)).unwrap_or_default() + } + + fn file_hyperlink(&mut self, path: &std::path::Path) -> Option<url::Url> { + let mut url = url::Url::from_file_path(path).ok()?; + // Do a best-effort of setting the host in the URL to avoid issues with opening a link + // scoped to the computer you've SSHed into + let hostname = if cfg!(windows) { + // Not supported correctly on windows + None + } else { + if let Some(hostname) = self.hostname.as_deref() { + Some(hostname) + } else { + self.hostname = hostname().ok().and_then(|h| h.into_string().ok()); + self.hostname.as_deref() + } + }; + let _ = url.set_host(hostname); + Some(url) } /// Prints a message to stderr and translates ANSI escape code into console colors. @@ -416,28 +473,6 @@ impl ShellOut { Ok(()) } - /// Write a styled fragment - fn write_stdout(&mut self, fragment: impl fmt::Display, style: &Style) -> CargoResult<()> { - let style = style.render(); - let reset = anstyle::Reset.render(); - - let mut buffer = Vec::new(); - write!(buffer, "{style}{}{reset}", fragment)?; - self.stdout().write_all(&buffer)?; - Ok(()) - } - - /// Write a styled fragment - fn write_stderr(&mut self, fragment: impl fmt::Display, style: &Style) -> CargoResult<()> { - let style = style.render(); - let reset = anstyle::Reset.render(); - - let mut buffer = Vec::new(); - write!(buffer, "{style}{}{reset}", fragment)?; - self.stderr().write_all(&buffer)?; - Ok(()) - } - /// Gets stdout as a `io::Write`. fn stdout(&mut self) -> &mut dyn Write { match *self { @@ -475,6 +510,44 @@ fn supports_color(choice: anstream::ColorChoice) -> bool { } } +fn supports_hyperlinks() -> bool { + #[allow(clippy::disallowed_methods)] // We are reading the state of the system, not config + if std::env::var_os("TERM_PROGRAM").as_deref() == Some(std::ffi::OsStr::new("iTerm.app")) { + // Override `supports_hyperlinks` as we have an unknown incompatibility with iTerm2 + return false; + } + + supports_hyperlinks::supports_hyperlinks() +} + +pub struct Hyperlink<D: fmt::Display> { + url: Option<D>, +} + +impl<D: fmt::Display> Default for Hyperlink<D> { + fn default() -> Self { + Self { url: None } + } +} + +impl<D: fmt::Display> Hyperlink<D> { + pub fn open(&self) -> impl fmt::Display { + if let Some(url) = self.url.as_ref() { + format!("\x1B]8;;{url}\x1B\\") + } else { + String::new() + } + } + + pub fn close(&self) -> impl fmt::Display { + if self.url.is_some() { + "\x1B]8;;\x1B\\" + } else { + "" + } + } +} + #[cfg(unix)] mod imp { use super::{Shell, TtyWidth}; diff --git a/src/tools/cargo/src/cargo/core/source_id.rs b/src/tools/cargo/src/cargo/core/source_id.rs index d688b8739..e53b1704d 100644 --- a/src/tools/cargo/src/cargo/core/source_id.rs +++ b/src/tools/cargo/src/cargo/core/source_id.rs @@ -3,7 +3,8 @@ use crate::sources::registry::CRATES_IO_HTTP_INDEX; use crate::sources::source::Source; use crate::sources::{DirectorySource, CRATES_IO_DOMAIN, CRATES_IO_INDEX, CRATES_IO_REGISTRY}; use crate::sources::{GitSource, PathSource, RegistrySource}; -use crate::util::{config, CanonicalUrl, CargoResult, Config, IntoUrl, ToSemver}; +use crate::util::interning::InternedString; +use crate::util::{config, CanonicalUrl, CargoResult, Config, IntoUrl}; use anyhow::Context; use serde::de; use serde::ser; @@ -50,7 +51,7 @@ struct SourceIdInner { /// The source kind. kind: SourceKind, /// For example, the exact Git revision of the specified branch for a Git Source. - precise: Option<String>, + precise: Option<Precise>, /// Name of the remote registry. /// /// WARNING: this is not always set when the name is not known, @@ -58,6 +59,29 @@ struct SourceIdInner { registry_key: Option<KeyOf>, } +#[derive(Eq, PartialEq, Clone, Debug, Hash)] +enum Precise { + Locked, + Updated { + name: InternedString, + from: semver::Version, + to: semver::Version, + }, + GitUrlFragment(String), +} + +impl fmt::Display for Precise { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Precise::Locked => "locked".fmt(f), + Precise::Updated { name, from, to } => { + write!(f, "{name}={from}->{to}") + } + Precise::GitUrlFragment(s) => s.fmt(f), + } + } +} + /// The possible kinds of code source. /// Along with [`SourceIdInner`], this fully defines the source. #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -164,31 +188,19 @@ impl SourceId { match kind { "git" => { let mut url = url.into_url()?; - let mut reference = GitReference::DefaultBranch; - for (k, v) in url.query_pairs() { - match &k[..] { - // Map older 'ref' to branch. - "branch" | "ref" => reference = GitReference::Branch(v.into_owned()), - - "rev" => reference = GitReference::Rev(v.into_owned()), - "tag" => reference = GitReference::Tag(v.into_owned()), - _ => {} - } - } + let reference = GitReference::from_query(url.query_pairs()); let precise = url.fragment().map(|s| s.to_owned()); url.set_fragment(None); url.set_query(None); - Ok(SourceId::for_git(&url, reference)?.with_precise(precise)) + Ok(SourceId::for_git(&url, reference)?.with_git_precise(precise)) } "registry" => { let url = url.into_url()?; - Ok(SourceId::new(SourceKind::Registry, url, None)? - .with_precise(Some("locked".to_string()))) + Ok(SourceId::new(SourceKind::Registry, url, None)?.with_locked_precise()) } "sparse" => { let url = string.into_url()?; - Ok(SourceId::new(SourceKind::SparseRegistry, url, None)? - .with_precise(Some("locked".to_string()))) + Ok(SourceId::new(SourceKind::SparseRegistry, url, None)?.with_locked_precise()) } "path" => { let url = url.into_url()?; @@ -332,10 +344,10 @@ impl SourceId { pub fn display_registry_name(self) -> String { if let Some(key) = self.inner.registry_key.as_ref().map(|k| k.key()) { key.into() - } else if self.precise().is_some() { + } else if self.has_precise() { // We remove `precise` here to retrieve an permissive version of // `SourceIdInner`, which may contain the registry name. - self.with_precise(None).display_registry_name() + self.without_precise().display_registry_name() } else { url_display(self.url()) } @@ -444,37 +456,81 @@ impl SourceId { } } - /// Gets the value of the precise field. - pub fn precise(self) -> Option<&'static str> { - self.inner.precise.as_deref() + /// Check if the precise data field has bean set + pub fn has_precise(self) -> bool { + self.inner.precise.is_some() + } + + /// Check if the precise data field has bean set to "locked" + pub fn has_locked_precise(self) -> bool { + self.inner.precise == Some(Precise::Locked) + } + + /// Check if two sources have the same precise data field + pub fn has_same_precise_as(self, other: Self) -> bool { + self.inner.precise == other.inner.precise } /// Check if the precise data field stores information for this `name` /// from a call to [SourceId::with_precise_registry_version]. /// /// If so return the version currently in the lock file and the version to be updated to. - /// If specified, our own source will have a precise version listed of the form - // `<pkg>=<p_req>-><f_req>` where `<pkg>` is the name of a crate on - // this source, `<p_req>` is the version installed and `<f_req>` is the - // version requested (argument to `--precise`). pub fn precise_registry_version( self, - name: &str, - ) -> Option<(semver::Version, semver::Version)> { - self.inner - .precise - .as_deref() - .and_then(|p| p.strip_prefix(name)?.strip_prefix('=')) - .map(|p| { - let (current, requested) = p.split_once("->").unwrap(); - (current.to_semver().unwrap(), requested.to_semver().unwrap()) - }) + pkg: &str, + ) -> Option<(&semver::Version, &semver::Version)> { + match &self.inner.precise { + Some(Precise::Updated { name, from, to }) if name == pkg => Some((from, to)), + _ => None, + } + } + + pub fn precise_git_fragment(self) -> Option<&'static str> { + match &self.inner.precise { + Some(Precise::GitUrlFragment(s)) => Some(&s[..8]), + _ => None, + } + } + + pub fn precise_git_oid(self) -> CargoResult<Option<git2::Oid>> { + Ok(match self.inner.precise.as_ref() { + Some(Precise::GitUrlFragment(s)) => { + Some(git2::Oid::from_str(s).with_context(|| { + format!("precise value for git is not a git revision: {}", s) + })?) + } + _ => None, + }) } /// Creates a new `SourceId` from this source with the given `precise`. - pub fn with_precise(self, v: Option<String>) -> SourceId { + pub fn with_git_precise(self, fragment: Option<String>) -> SourceId { + SourceId::wrap(SourceIdInner { + precise: fragment.map(|f| Precise::GitUrlFragment(f)), + ..(*self.inner).clone() + }) + } + + /// Creates a new `SourceId` from this source without a `precise`. + pub fn without_precise(self) -> SourceId { SourceId::wrap(SourceIdInner { - precise: v, + precise: None, + ..(*self.inner).clone() + }) + } + + /// Creates a new `SourceId` from this source without a `precise`. + pub fn with_locked_precise(self) -> SourceId { + SourceId::wrap(SourceIdInner { + precise: Some(Precise::Locked), + ..(*self.inner).clone() + }) + } + + /// Creates a new `SourceId` from this source with the `precise` from some other `SourceId`. + pub fn with_precise_from(self, v: Self) -> SourceId { + SourceId::wrap(SourceIdInner { + precise: v.inner.precise.clone(), ..(*self.inner).clone() }) } @@ -487,13 +543,21 @@ impl SourceId { /// The data can be read with [SourceId::precise_registry_version] pub fn with_precise_registry_version( self, - name: impl fmt::Display, - version: &semver::Version, + name: InternedString, + version: semver::Version, precise: &str, ) -> CargoResult<SourceId> { - semver::Version::parse(precise) + let precise = semver::Version::parse(precise) .with_context(|| format!("invalid version format for precise version `{precise}`"))?; - Ok(self.with_precise(Some(format!("{}={}->{}", name, version, precise)))) + + Ok(SourceId::wrap(SourceIdInner { + precise: Some(Precise::Updated { + name, + from: version, + to: precise, + }), + ..(*self.inner).clone() + })) } /// Returns `true` if the remote registry is the standard <https://crates.io>. @@ -625,7 +689,8 @@ impl fmt::Display for SourceId { write!(f, "?{}", pretty)?; } - if let Some(ref s) = self.inner.precise { + if let Some(s) = &self.inner.precise { + let s = s.to_string(); let len = cmp::min(s.len(), 8); write!(f, "#{}", &s[..len])?; } @@ -677,6 +742,20 @@ impl PartialEq for SourceIdInner { } } +impl SourceKind { + pub(crate) fn protocol(&self) -> Option<&str> { + match self { + SourceKind::Path => Some("path"), + SourceKind::Git(_) => Some("git"), + SourceKind::Registry => Some("registry"), + // Sparse registry URL already includes the `sparse+` prefix + SourceKind::SparseRegistry => None, + SourceKind::LocalRegistry => Some("local-registry"), + SourceKind::Directory => Some("directory"), + } + } +} + /// Forwards to `Ord` impl PartialOrd for SourceKind { fn partial_cmp(&self, other: &SourceKind) -> Option<Ordering> { @@ -773,57 +852,46 @@ pub struct SourceIdAsUrl<'a> { impl<'a> fmt::Display for SourceIdAsUrl<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self.inner { - SourceIdInner { - kind: SourceKind::Path, - ref url, - .. - } => write!(f, "path+{}", url), - SourceIdInner { - kind: SourceKind::Git(ref reference), - ref url, - ref precise, - .. - } => { - write!(f, "git+{}", url)?; - if let Some(pretty) = reference.pretty_ref(self.encoded) { - write!(f, "?{}", pretty)?; - } - if let Some(precise) = precise.as_ref() { - write!(f, "#{}", precise)?; - } - Ok(()) - } - SourceIdInner { - kind: SourceKind::Registry, - ref url, - .. - } => { - write!(f, "registry+{url}") + if let Some(protocol) = self.inner.kind.protocol() { + write!(f, "{protocol}+")?; + } + write!(f, "{}", self.inner.url)?; + if let SourceIdInner { + kind: SourceKind::Git(ref reference), + ref precise, + .. + } = *self.inner + { + if let Some(pretty) = reference.pretty_ref(self.encoded) { + write!(f, "?{}", pretty)?; } - SourceIdInner { - kind: SourceKind::SparseRegistry, - ref url, - .. - } => { - // Sparse registry URL already includes the `sparse+` prefix - write!(f, "{url}") + if let Some(precise) = precise.as_ref() { + write!(f, "#{}", precise)?; } - SourceIdInner { - kind: SourceKind::LocalRegistry, - ref url, - .. - } => write!(f, "local-registry+{}", url), - SourceIdInner { - kind: SourceKind::Directory, - ref url, - .. - } => write!(f, "directory+{}", url), } + Ok(()) } } impl GitReference { + pub fn from_query( + query_pairs: impl Iterator<Item = (impl AsRef<str>, impl AsRef<str>)>, + ) -> Self { + let mut reference = GitReference::DefaultBranch; + for (k, v) in query_pairs { + let v = v.as_ref(); + match k.as_ref() { + // Map older 'ref' to branch. + "branch" | "ref" => reference = GitReference::Branch(v.to_owned()), + + "rev" => reference = GitReference::Rev(v.to_owned()), + "tag" => reference = GitReference::Tag(v.to_owned()), + _ => {} + } + } + reference + } + /// Returns a `Display`able view of this git reference, or None if using /// the head of the default branch pub fn pretty_ref(&self, url_encoded: bool) -> Option<PrettyRef<'_>> { diff --git a/src/tools/cargo/src/cargo/core/summary.rs b/src/tools/cargo/src/cargo/core/summary.rs index 128c0db9c..243f6b398 100644 --- a/src/tools/cargo/src/cargo/core/summary.rs +++ b/src/tools/cargo/src/cargo/core/summary.rs @@ -431,6 +431,9 @@ impl fmt::Display for FeatureValue { pub type FeatureMap = BTreeMap<InternedString, Vec<FeatureValue>>; fn validate_feature_name(pkg_id: PackageId, name: &str) -> CargoResult<()> { + if name.is_empty() { + bail!("feature name cannot be empty"); + } let mut chars = name.chars(); if let Some(ch) = chars.next() { if !(unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_' || ch.is_digit(10)) { @@ -448,7 +451,7 @@ fn validate_feature_name(pkg_id: PackageId, name: &str) -> CargoResult<()> { if !(unicode_xid::UnicodeXID::is_xid_continue(ch) || ch == '-' || ch == '+' || ch == '.') { bail!( "invalid character `{}` in feature `{}` in package {}, \ - characters must be Unicode XID characters, `+`, or `.` \ + characters must be Unicode XID characters, '-', `+`, or `.` \ (numbers, `+`, `-`, `_`, `.`, or most letters)", ch, name, @@ -488,5 +491,6 @@ mod tests { assert!(validate_feature_name(pkg_id, "?foo").is_err()); assert!(validate_feature_name(pkg_id, "ⒶⒷⒸ").is_err()); assert!(validate_feature_name(pkg_id, "a¼").is_err()); + assert!(validate_feature_name(pkg_id, "").is_err()); } } diff --git a/src/tools/cargo/src/cargo/core/workspace.rs b/src/tools/cargo/src/cargo/core/workspace.rs index db379d780..4667c8029 100644 --- a/src/tools/cargo/src/cargo/core/workspace.rs +++ b/src/tools/cargo/src/cargo/core/workspace.rs @@ -22,7 +22,9 @@ use crate::sources::{PathSource, CRATES_IO_INDEX, CRATES_IO_REGISTRY}; use crate::util::edit_distance; use crate::util::errors::{CargoResult, ManifestError}; use crate::util::interning::InternedString; -use crate::util::toml::{read_manifest, InheritableFields, TomlDependency, TomlProfiles}; +use crate::util::toml::{ + read_manifest, schema::InheritableFields, schema::TomlDependency, schema::TomlProfiles, +}; use crate::util::RustVersion; use crate::util::{config::ConfigRelativePath, Config, Filesystem, IntoUrl}; use cargo_util::paths; @@ -1491,7 +1493,7 @@ impl<'cfg> Workspace<'cfg> { // Check if `dep_name` is member of the workspace, but isn't associated with current package. self.current_opt() != Some(member) && member.name() == *dep_name }); - if is_member && specs.iter().any(|spec| spec.name() == *dep_name) { + if is_member && specs.iter().any(|spec| spec.name() == dep_name.as_str()) { member_specific_features .entry(*dep_name) .or_default() diff --git a/src/tools/cargo/src/cargo/lib.rs b/src/tools/cargo/src/cargo/lib.rs index 908ff4ecc..6947642c9 100644 --- a/src/tools/cargo/src/cargo/lib.rs +++ b/src/tools/cargo/src/cargo/lib.rs @@ -93,7 +93,7 @@ //! Files that interact with cargo include //! //! - Package -//! - `Cargo.toml`: User-written project manifest, loaded with [`util::toml::TomlManifest`] and then +//! - `Cargo.toml`: User-written project manifest, loaded with [`util::toml::schema::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`] @@ -161,6 +161,7 @@ pub mod core; pub mod ops; pub mod sources; pub mod util; +pub mod util_semver; mod version; pub fn exit_with_error(err: CliError, shell: &mut Shell) -> ! { diff --git a/src/tools/cargo/src/cargo/ops/cargo_add/mod.rs b/src/tools/cargo/src/cargo/ops/cargo_add/mod.rs index 968d6068f..39e37b156 100644 --- a/src/tools/cargo/src/cargo/ops/cargo_add/mod.rs +++ b/src/tools/cargo/src/cargo/ops/cargo_add/mod.rs @@ -23,6 +23,7 @@ use crate::core::Shell; use crate::core::Summary; use crate::core::Workspace; use crate::sources::source::QueryKind; +use crate::util::cache_lock::CacheLockMode; use crate::util::style; use crate::util::toml_mut::dependency::Dependency; use crate::util::toml_mut::dependency::GitSource; @@ -30,6 +31,7 @@ use crate::util::toml_mut::dependency::MaybeWorkspace; use crate::util::toml_mut::dependency::PathSource; use crate::util::toml_mut::dependency::Source; use crate::util::toml_mut::dependency::WorkspaceSource; +use crate::util::toml_mut::is_sorted; use crate::util::toml_mut::manifest::DepTable; use crate::util::toml_mut::manifest::LocalManifest; use crate::util::RustVersion; @@ -77,7 +79,9 @@ pub fn add(workspace: &Workspace<'_>, options: &AddOptions<'_>) -> CargoResult<( let mut registry = PackageRegistry::new(options.config)?; let deps = { - let _lock = options.config.acquire_package_cache_lock()?; + let _lock = options + .config + .acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?; registry.lock_patches(); options .dependencies @@ -944,12 +948,17 @@ fn print_dep_table_msg(shell: &mut Shell, dep: &DependencyUI) -> CargoResult<()> return Ok(()); } + let stderr = shell.err(); + let good = style::GOOD.render(); + let error = style::ERROR.render(); + let reset = anstyle::Reset.render(); + let (activated, deactivated) = dep.features(); if !activated.is_empty() || !deactivated.is_empty() { let prefix = format!("{:>13}", " "); let suffix = format_features_version_suffix(&dep); - shell.write_stderr(format_args!("{prefix}Features{suffix}:\n"), &style::NOP)?; + writeln!(stderr, "{prefix}Features{suffix}:")?; const MAX_FEATURE_PRINTS: usize = 30; let total_activated = activated.len(); @@ -957,28 +966,18 @@ fn print_dep_table_msg(shell: &mut Shell, dep: &DependencyUI) -> CargoResult<()> if total_activated <= MAX_FEATURE_PRINTS { for feat in activated { - shell.write_stderr(&prefix, &style::NOP)?; - shell.write_stderr('+', &style::GOOD)?; - shell.write_stderr(format_args!(" {feat}\n"), &style::NOP)?; + writeln!(stderr, "{prefix}{good}+{reset} {feat}")?; } } else { - shell.write_stderr( - format_args!("{prefix}{total_activated} activated features\n"), - &style::NOP, - )?; + writeln!(stderr, "{prefix}{total_activated} activated features")?; } if total_activated + total_deactivated <= MAX_FEATURE_PRINTS { for feat in deactivated { - shell.write_stderr(&prefix, &style::NOP)?; - shell.write_stderr('-', &style::ERROR)?; - shell.write_stderr(format_args!(" {feat}\n"), &style::NOP)?; + writeln!(stderr, "{prefix}{error}-{reset} {feat}")?; } } else { - shell.write_stderr( - format_args!("{prefix}{total_deactivated} deactivated features\n"), - &style::NOP, - )?; + writeln!(stderr, "{prefix}{total_deactivated} deactivated features")?; } } @@ -1006,22 +1005,6 @@ fn format_features_version_suffix(dep: &DependencyUI) -> String { } } -// Based on Iterator::is_sorted from nightly std; remove in favor of that when stabilized. -fn is_sorted(mut it: impl Iterator<Item = impl PartialOrd>) -> bool { - let Some(mut last) = it.next() else { - return true; - }; - - for curr in it { - if curr < last { - return false; - } - last = curr; - } - - true -} - fn find_workspace_dep(toml_key: &str, root_manifest: &Path) -> CargoResult<Dependency> { let manifest = LocalManifest::try_new(root_manifest)?; let manifest = manifest diff --git a/src/tools/cargo/src/cargo/ops/cargo_compile/mod.rs b/src/tools/cargo/src/cargo/ops/cargo_compile/mod.rs index 9cf8599c4..94c6cf9de 100644 --- a/src/tools/cargo/src/cargo/ops/cargo_compile/mod.rs +++ b/src/tools/cargo/src/cargo/ops/cargo_compile/mod.rs @@ -493,7 +493,7 @@ pub fn create_bcx<'a, 'cfg>( continue; }; - let req = version.caret_req(); + let req = version.to_caret_req(); if req.matches(&untagged_version) { continue; } diff --git a/src/tools/cargo/src/cargo/ops/cargo_doc.rs b/src/tools/cargo/src/cargo/ops/cargo_doc.rs index afa6ac327..ecc17e9fc 100644 --- a/src/tools/cargo/src/cargo/ops/cargo_doc.rs +++ b/src/tools/cargo/src/cargo/ops/cargo_doc.rs @@ -34,11 +34,31 @@ pub fn doc(ws: &Workspace<'_>, options: &DocOptions) -> CargoResult<()> { let cfg: Option<PathAndArgs> = ws.config().get("doc.browser")?; cfg.map(|path_args| (path_args.path.resolve_program(ws.config()), path_args.args)) }; - let mut shell = ws.config().shell(); - shell.status("Opening", path.display())?; + let link = shell.err_file_hyperlink(&path); + shell.status( + "Opening", + format!("{}{}{}", link.open(), path.display(), link.close()), + )?; open_docs(&path, &mut shell, config_browser, ws.config())?; } + } else { + for name in &compilation.root_crate_names { + for kind in &options.compile_opts.build_config.requested_kinds { + let path = compilation.root_output[&kind] + .with_file_name("doc") + .join(&name) + .join("index.html"); + if path.exists() { + let mut shell = ws.config().shell(); + let link = shell.err_file_hyperlink(&path); + shell.status( + "Generated", + format!("{}{}{}", link.open(), path.display(), link.close()), + )?; + } + } + } } Ok(()) diff --git a/src/tools/cargo/src/cargo/ops/cargo_generate_lockfile.rs b/src/tools/cargo/src/cargo/ops/cargo_generate_lockfile.rs index a83a92ccc..a16d6d403 100644 --- a/src/tools/cargo/src/cargo/ops/cargo_generate_lockfile.rs +++ b/src/tools/cargo/src/cargo/ops/cargo_generate_lockfile.rs @@ -3,10 +3,12 @@ use crate::core::resolver::features::{CliFeatures, HasDevUnits}; use crate::core::{PackageId, PackageIdSpec}; use crate::core::{Resolve, SourceId, Workspace}; use crate::ops; +use crate::util::cache_lock::CacheLockMode; use crate::util::config::Config; use crate::util::style; use crate::util::CargoResult; use anstyle::Style; +use std::cmp::Ordering; use std::collections::{BTreeMap, HashSet}; use tracing::debug; @@ -48,7 +50,9 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes // Updates often require a lot of modifications to the registry, so ensure // that we're synchronized against other Cargos. - let _lock = ws.config().acquire_package_cache_lock()?; + let _lock = ws + .config() + .acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?; let max_rust_version = ws.rust_version(); @@ -101,14 +105,14 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes if pid.source_id().is_registry() { pid.source_id().with_precise_registry_version( pid.name(), - pid.version(), + pid.version().clone(), precise, )? } else { - pid.source_id().with_precise(Some(precise.to_string())) + pid.source_id().with_git_precise(Some(precise.to_string())) } } - None => pid.source_id().with_precise(None), + None => pid.source_id().without_precise(), }); } if let Ok(unused_id) = @@ -143,13 +147,17 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes format!( "{} -> #{}", removed[0], - &added[0].source_id().precise().unwrap()[..8] + &added[0].source_id().precise_git_fragment().unwrap() ) } else { format!("{} -> v{}", removed[0], added[0].version()) }; - if removed[0].version() > added[0].version() { + // If versions differ only in build metadata, we call it an "update" + // regardless of whether the build metadata has gone up or down. + // This metadata is often stuff like git commit hashes, which are + // not meaningfully ordered. + if removed[0].version().cmp_precedence(added[0].version()) == Ordering::Greater { print_change("Downgrading", msg, &style::WARN)?; } else { print_change("Updating", msg, &style::GOOD)?; @@ -222,7 +230,7 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes b[i..] .iter() .take_while(|b| a == b) - .all(|b| a.source_id().precise() != b.source_id().precise()) + .all(|b| !a.source_id().has_same_precise_as(b.source_id())) }) .cloned() .collect() diff --git a/src/tools/cargo/src/cargo/ops/cargo_install.rs b/src/tools/cargo/src/cargo/ops/cargo_install.rs index 957ab43e6..16027233e 100644 --- a/src/tools/cargo/src/cargo/ops/cargo_install.rs +++ b/src/tools/cargo/src/cargo/ops/cargo_install.rs @@ -68,6 +68,7 @@ impl<'cfg> InstallablePackage<'cfg> { force: bool, no_track: bool, needs_update_if_source_is_index: bool, + current_rust_version: Option<&semver::Version>, ) -> CargoResult<Option<Self>> { if let Some(name) = krate { if name == "." { @@ -105,6 +106,7 @@ impl<'cfg> InstallablePackage<'cfg> { dep, |git: &mut GitSource<'_>| git.read_packages(), config, + current_rust_version, )? } else if source_id.is_path() { let mut src = path_source(source_id, config)?; @@ -142,6 +144,7 @@ impl<'cfg> InstallablePackage<'cfg> { dep, |path: &mut PathSource<'_>| path.read_packages(), config, + current_rust_version, )? } else if let Some(dep) = dep { let mut source = map.load(source_id, &HashSet::new())?; @@ -161,7 +164,13 @@ impl<'cfg> InstallablePackage<'cfg> { config.shell().status("Ignored", &msg)?; return Ok(None); } - select_dep_pkg(&mut source, dep, config, needs_update_if_source_is_index)? + select_dep_pkg( + &mut source, + dep, + config, + needs_update_if_source_is_index, + current_rust_version, + )? } else { bail!( "must specify a crate to install from \ @@ -616,6 +625,21 @@ pub fn install( let dst = root.join("bin").into_path_unlocked(); let map = SourceConfigMap::new(config)?; + let current_rust_version = if opts.honor_rust_version { + let rustc = config.load_global_rustc(None)?; + + // Remove any pre-release identifiers for easier comparison + let current_version = &rustc.version; + let untagged_version = semver::Version::new( + current_version.major, + current_version.minor, + current_version.patch, + ); + Some(untagged_version) + } else { + None + }; + let (installed_anything, scheduled_error) = if krates.len() <= 1 { let (krate, vers) = krates .iter() @@ -623,7 +647,18 @@ pub fn install( .map(|(k, v)| (Some(k.as_str()), v.as_ref())) .unwrap_or((None, None)); let installable_pkg = InstallablePackage::new( - config, root, map, krate, source_id, from_cwd, vers, opts, force, no_track, true, + config, + root, + map, + krate, + source_id, + from_cwd, + vers, + opts, + force, + no_track, + true, + current_rust_version.as_ref(), )?; let mut installed_anything = true; if let Some(installable_pkg) = installable_pkg { @@ -654,6 +689,7 @@ pub fn install( force, no_track, !did_update, + current_rust_version.as_ref(), ) { Ok(Some(installable_pkg)) => { did_update = true; @@ -773,7 +809,7 @@ where // expensive network call in the case that the package is already installed. // If this fails, the caller will possibly do an index update and try again, this is just a // best-effort check to see if we can avoid hitting the network. - if let Ok(pkg) = select_dep_pkg(source, dep, config, false) { + if let Ok(pkg) = select_dep_pkg(source, dep, config, false, None) { let (_ws, rustc, target) = make_ws_rustc_target(config, opts, &source.source_id(), pkg.clone())?; if let Ok(true) = is_installed(&pkg, config, opts, &rustc, &target, root, dst, force) { diff --git a/src/tools/cargo/src/cargo/ops/cargo_new.rs b/src/tools/cargo/src/cargo/ops/cargo_new.rs index 78b3cc4f6..1c06b5f82 100644 --- a/src/tools/cargo/src/cargo/ops/cargo_new.rs +++ b/src/tools/cargo/src/cargo/ops/cargo_new.rs @@ -1,10 +1,11 @@ use crate::core::{Edition, Shell, Workspace}; use crate::util::errors::CargoResult; use crate::util::important_paths::find_root_manifest_for_wd; +use crate::util::toml_mut::is_sorted; use crate::util::{existing_vcs_repo, FossilRepo, GitRepo, HgRepo, PijulRepo}; use crate::util::{restricted_names, Config}; -use anyhow::{anyhow, Context as _}; -use cargo_util::paths; +use anyhow::{anyhow, Context}; +use cargo_util::paths::{self, write_atomic}; use serde::de; use serde::Deserialize; use std::collections::BTreeMap; @@ -13,6 +14,7 @@ use std::io::{BufRead, BufReader, ErrorKind}; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::{fmt, slice}; +use toml_edit::{Array, Value}; #[derive(Clone, Copy, Debug, PartialEq)] pub enum VersionControl { @@ -258,6 +260,12 @@ fn check_name( name ))?; } + let name_in_lowercase = name.to_lowercase(); + if name != name_in_lowercase { + shell.warn(format!( + "the name `{name}` is not snake_case or kebab-case which is recommended for package names, consider `{name_in_lowercase}`" + ))?; + } Ok(()) } @@ -803,7 +811,7 @@ fn mk(config: &Config, opts: &MkOptions<'_>) -> CargoResult<()> { // Sometimes the root manifest is not a valid manifest, so we only try to parse it if it is. // This should not block the creation of the new project. It is only a best effort to // inherit the workspace package keys. - if let Ok(workspace_document) = root_manifest.parse::<toml_edit::Document>() { + if let Ok(mut workspace_document) = root_manifest.parse::<toml_edit::Document>() { if let Some(workspace_package_keys) = workspace_document .get("workspace") .and_then(|workspace| workspace.get("package")) @@ -826,6 +834,13 @@ fn mk(config: &Config, opts: &MkOptions<'_>) -> CargoResult<()> { table["workspace"] = toml_edit::value(true); manifest["lints"] = toml_edit::Item::Table(table); } + + // Try to add the new package to the workspace members. + update_manifest_with_new_member( + &root_manifest_path, + &mut workspace_document, + opts.path, + )?; } } @@ -925,3 +940,95 @@ fn update_manifest_with_inherited_workspace_package_keys( try_remove_and_inherit_package_key(key, manifest); } } + +/// Adds the new package member to the [workspace.members] array. +/// - It first checks if the name matches any element in [workspace.exclude], +/// and it ignores the name if there is a match. +/// - Then it check if the name matches any element already in [workspace.members], +/// and it ignores the name if there is a match. +/// - If [workspace.members] doesn't exist in the manifest, it will add a new section +/// with the new package in it. +fn update_manifest_with_new_member( + root_manifest_path: &Path, + workspace_document: &mut toml_edit::Document, + package_path: &Path, +) -> CargoResult<()> { + // Find the relative path for the package from the workspace root directory. + let workspace_root = root_manifest_path.parent().with_context(|| { + format!( + "workspace root manifest doesn't have a parent directory `{}`", + root_manifest_path.display() + ) + })?; + let relpath = pathdiff::diff_paths(package_path, workspace_root).with_context(|| { + format!( + "path comparison requires two absolute paths; package_path: `{}`, workspace_path: `{}`", + package_path.display(), + workspace_root.display() + ) + })?; + + let mut components = Vec::new(); + for comp in relpath.iter() { + let comp = comp.to_str().with_context(|| { + format!("invalid unicode component in path `{}`", relpath.display()) + })?; + components.push(comp); + } + let display_path = components.join("/"); + + // Don't add the new package to the workspace's members + // if there is an exclusion match for it. + if let Some(exclude) = workspace_document + .get("workspace") + .and_then(|workspace| workspace.get("exclude")) + .and_then(|exclude| exclude.as_array()) + { + for member in exclude { + let pat = member + .as_str() + .with_context(|| format!("invalid non-string exclude path `{}`", member))?; + if pat == display_path { + return Ok(()); + } + } + } + + // If the members element already exist, check if one of the patterns + // in the array already includes the new package's relative path. + // - Add the relative path if the members don't match the new package's path. + // - Create a new members array if there are no members element in the workspace yet. + if let Some(members) = workspace_document + .get_mut("workspace") + .and_then(|workspace| workspace.get_mut("members")) + .and_then(|members| members.as_array_mut()) + { + for member in members.iter() { + let pat = member + .as_str() + .with_context(|| format!("invalid non-string member `{}`", member))?; + let pattern = glob::Pattern::new(pat) + .with_context(|| format!("cannot build glob pattern from `{}`", pat))?; + + if pattern.matches(&display_path) { + return Ok(()); + } + } + + let was_sorted = is_sorted(members.iter().map(Value::as_str)); + members.push(&display_path); + if was_sorted { + members.sort_by(|lhs, rhs| lhs.as_str().cmp(&rhs.as_str())); + } + } else { + let mut array = Array::new(); + array.push(&display_path); + + workspace_document["workspace"]["members"] = toml_edit::value(array); + } + + write_atomic( + &root_manifest_path, + workspace_document.to_string().to_string().as_bytes(), + ) +} diff --git a/src/tools/cargo/src/cargo/ops/cargo_package.rs b/src/tools/cargo/src/cargo/ops/cargo_package.rs index b6423aa96..6ac09dc77 100644 --- a/src/tools/cargo/src/cargo/ops/cargo_package.rs +++ b/src/tools/cargo/src/cargo/ops/cargo_package.rs @@ -3,7 +3,6 @@ use std::fs::{self, File}; use std::io::prelude::*; use std::io::SeekFrom; use std::path::{Path, PathBuf}; -use std::rc::Rc; use std::sync::Arc; use std::task::Poll; @@ -13,9 +12,10 @@ use crate::core::{registry::PackageRegistry, resolver::HasDevUnits}; use crate::core::{Feature, Shell, Verbosity, Workspace}; use crate::core::{Package, PackageId, PackageSet, Resolve, SourceId}; use crate::sources::PathSource; +use crate::util::cache_lock::CacheLockMode; use crate::util::config::JobsConfig; use crate::util::errors::CargoResult; -use crate::util::toml::TomlManifest; +use crate::util::toml::schema::TomlManifest; use crate::util::{self, human_readable_bytes, restricted_names, Config, FileLock}; use crate::{drop_println, ops}; use anyhow::Context as _; @@ -132,7 +132,7 @@ pub fn package_one( let dir = ws.target_dir().join("package"); let mut dst = { let tmp = format!(".{}", filename); - dir.open_rw(&tmp, config, "package scratch space")? + dir.open_rw_exclusive_create(&tmp, config, "package scratch space")? }; // Package up and test a temporary tarball and only move it to the final @@ -411,16 +411,14 @@ fn build_lock(ws: &Workspace<'_>, orig_pkg: &Package) -> CargoResult<String> { let orig_resolve = ops::load_pkg_lockfile(ws)?; // Convert Package -> TomlManifest -> Manifest -> Package - let toml_manifest = Rc::new( - orig_pkg - .manifest() - .original() - .prepare_for_publish(ws, orig_pkg.root())?, - ); + let toml_manifest = orig_pkg + .manifest() + .original() + .prepare_for_publish(ws, orig_pkg.root())?; let package_root = orig_pkg.root(); let source_id = orig_pkg.package_id().source_id(); let (manifest, _nested_paths) = - TomlManifest::to_real_manifest(&toml_manifest, false, source_id, package_root, config)?; + TomlManifest::to_real_manifest(toml_manifest, false, source_id, package_root, config)?; let new_pkg = Package::new(manifest, orig_pkg.manifest_path()); let max_rust_version = new_pkg.rust_version().cloned(); @@ -806,7 +804,7 @@ pub fn check_yanked( ) -> CargoResult<()> { // Checking the yanked status involves taking a look at the registry and // maybe updating files, so be sure to lock it here. - let _lock = config.acquire_package_cache_lock()?; + let _lock = config.acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?; let mut sources = pkg_set.sources_mut(); let mut pending: Vec<PackageId> = resolve.iter().collect(); diff --git a/src/tools/cargo/src/cargo/ops/cargo_uninstall.rs b/src/tools/cargo/src/cargo/ops/cargo_uninstall.rs index 355154418..1f22e191e 100644 --- a/src/tools/cargo/src/cargo/ops/cargo_uninstall.rs +++ b/src/tools/cargo/src/cargo/ops/cargo_uninstall.rs @@ -90,6 +90,7 @@ fn uninstall_cwd(root: &Filesystem, bins: &[String], config: &Config) -> CargoRe None, |path: &mut PathSource<'_>| path.read_packages(), config, + None, )?; let pkgid = pkg.package_id(); uninstall_pkgid(root, tracker, pkgid, bins, config) diff --git a/src/tools/cargo/src/cargo/ops/common_for_install_and_uninstall.rs b/src/tools/cargo/src/cargo/ops/common_for_install_and_uninstall.rs index 0934cbd29..d1f9152be 100644 --- a/src/tools/cargo/src/cargo/ops/common_for_install_and_uninstall.rs +++ b/src/tools/cargo/src/cargo/ops/common_for_install_and_uninstall.rs @@ -17,6 +17,7 @@ use crate::ops::{self, CompileFilter, CompileOptions}; use crate::sources::source::QueryKind; use crate::sources::source::Source; use crate::sources::PathSource; +use crate::util::cache_lock::CacheLockMode; use crate::util::errors::CargoResult; use crate::util::Config; use crate::util::{FileLock, Filesystem}; @@ -97,8 +98,10 @@ pub struct CrateListingV1 { impl InstallTracker { /// Create an InstallTracker from information on disk. pub fn load(config: &Config, root: &Filesystem) -> CargoResult<InstallTracker> { - let v1_lock = root.open_rw(Path::new(".crates.toml"), config, "crate metadata")?; - let v2_lock = root.open_rw(Path::new(".crates2.json"), config, "crate metadata")?; + let v1_lock = + root.open_rw_exclusive_create(Path::new(".crates.toml"), config, "crate metadata")?; + let v2_lock = + root.open_rw_exclusive_create(Path::new(".crates2.json"), config, "crate metadata")?; let v1 = (|| -> CargoResult<_> { let mut contents = String::new(); @@ -212,7 +215,7 @@ impl InstallTracker { let precise_equal = if source_id.is_git() { // Git sources must have the exact same hash to be // considered "fresh". - dupe_pkg_id.source_id().precise() == source_id.precise() + dupe_pkg_id.source_id().has_same_precise_as(source_id) } else { true }; @@ -529,6 +532,7 @@ pub fn select_dep_pkg<T>( dep: Dependency, config: &Config, needs_update: bool, + current_rust_version: Option<&semver::Version>, ) -> CargoResult<Package> where T: Source, @@ -536,7 +540,7 @@ where // This operation may involve updating some sources or making a few queries // which may involve frobbing caches, as a result make sure we synchronize // with other global Cargos - let _lock = config.acquire_package_cache_lock()?; + let _lock = config.acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?; if needs_update { source.invalidate_cache(); @@ -548,9 +552,56 @@ where Poll::Pending => source.block_until_ready()?, } }; - match deps.iter().map(|p| p.package_id()).max() { - Some(pkgid) => { - let pkg = Box::new(source).download_now(pkgid, config)?; + match deps.iter().max_by_key(|p| p.package_id()) { + Some(summary) => { + if let (Some(current), Some(msrv)) = (current_rust_version, summary.rust_version()) { + let msrv_req = msrv.to_caret_req(); + if !msrv_req.matches(current) { + let name = summary.name(); + let ver = summary.version(); + let extra = if dep.source_id().is_registry() { + // Match any version, not just the selected + let msrv_dep = + Dependency::parse(dep.package_name(), None, dep.source_id())?; + let msrv_deps = loop { + match source.query_vec(&msrv_dep, QueryKind::Exact)? { + Poll::Ready(deps) => break deps, + Poll::Pending => source.block_until_ready()?, + } + }; + if let Some(alt) = msrv_deps + .iter() + .filter(|summary| { + summary + .rust_version() + .map(|msrv| msrv.to_caret_req().matches(current)) + .unwrap_or(true) + }) + .max_by_key(|s| s.package_id()) + { + if let Some(rust_version) = alt.rust_version() { + format!( + "\n`{name} {}` supports rustc {rust_version}", + alt.version() + ) + } else { + format!( + "\n`{name} {}` has an unspecified minimum rustc version", + alt.version() + ) + } + } else { + String::new() + } + } else { + String::new() + }; + bail!("\ +cannot install package `{name} {ver}`, it requires rustc {msrv} or newer, while the currently active rustc version is {current}{extra}" +) + } + } + let pkg = Box::new(source).download_now(summary.package_id(), config)?; Ok(pkg) } None => { @@ -596,6 +647,7 @@ pub fn select_pkg<T, F>( dep: Option<Dependency>, mut list_all: F, config: &Config, + current_rust_version: Option<&semver::Version>, ) -> CargoResult<Package> where T: Source, @@ -604,12 +656,12 @@ where // This operation may involve updating some sources or making a few queries // which may involve frobbing caches, as a result make sure we synchronize // with other global Cargos - let _lock = config.acquire_package_cache_lock()?; + let _lock = config.acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?; source.invalidate_cache(); return if let Some(dep) = dep { - select_dep_pkg(source, dep, config, false) + select_dep_pkg(source, dep, config, false, current_rust_version) } else { let candidates = list_all(source)?; let binaries = candidates diff --git a/src/tools/cargo/src/cargo/ops/fix.rs b/src/tools/cargo/src/cargo/ops/fix.rs index 9d6459294..6630914a4 100644 --- a/src/tools/cargo/src/cargo/ops/fix.rs +++ b/src/tools/cargo/src/cargo/ops/fix.rs @@ -451,6 +451,11 @@ pub fn fix_exec_rustc(config: &Config, lock_addr: &str) -> CargoResult<()> { // things like colored output to work correctly. rustc.arg(arg); } + // Removes `FD_CLOEXEC` set by `jobserver::Client` to pass jobserver + // as environment variables specify. + if let Some(client) = config.jobserver_from_env() { + rustc.inherit_jobserver(client); + } debug!("calling rustc to display remaining diagnostics: {rustc}"); exit_with(rustc.status()?); } diff --git a/src/tools/cargo/src/cargo/ops/lockfile.rs b/src/tools/cargo/src/cargo/ops/lockfile.rs index 26456e560..2160b6f01 100644 --- a/src/tools/cargo/src/cargo/ops/lockfile.rs +++ b/src/tools/cargo/src/cargo/ops/lockfile.rs @@ -12,7 +12,7 @@ pub fn load_pkg_lockfile(ws: &Workspace<'_>) -> CargoResult<Option<Resolve>> { return Ok(None); } - let mut f = lock_root.open_ro("Cargo.lock", ws.config(), "Cargo.lock file")?; + let mut f = lock_root.open_ro_shared("Cargo.lock", ws.config(), "Cargo.lock file")?; let mut s = String::new(); f.read_to_string(&mut s) @@ -64,22 +64,21 @@ pub fn write_pkg_lockfile(ws: &Workspace<'_>, resolve: &mut Resolve) -> CargoRes // out lock file updates as they're otherwise already updated, and changes // which don't touch dependencies won't seemingly spuriously update the lock // file. - if resolve.version() < ResolveVersion::default() { - resolve.set_version(ResolveVersion::default()); + let default_version = ResolveVersion::default(); + let current_version = resolve.version(); + let next_lockfile_bump = ws.config().cli_unstable().next_lockfile_bump; + + if current_version < default_version { + resolve.set_version(default_version); out = serialize_resolve(resolve, orig.as_deref()); - } else if resolve.version() > ResolveVersion::default() - && !ws.config().cli_unstable().next_lockfile_bump - { + } else if current_version > ResolveVersion::max_stable() && !next_lockfile_bump { // The next version hasn't yet stabilized. - anyhow::bail!( - "lock file version `{:?}` requires `-Znext-lockfile-bump`", - resolve.version() - ) + anyhow::bail!("lock file version `{current_version:?}` requires `-Znext-lockfile-bump`") } // Ok, if that didn't work just write it out lock_root - .open_rw("Cargo.lock", ws.config(), "Cargo.lock file") + .open_rw_exclusive_create("Cargo.lock", ws.config(), "Cargo.lock file") .and_then(|mut f| { f.file().set_len(0)?; f.write_all(out.as_bytes())?; @@ -100,7 +99,7 @@ fn resolve_to_string_orig( ) -> (Option<String>, String, Filesystem) { // Load the original lock file if it exists. let lock_root = lock_root(ws); - let orig = lock_root.open_ro("Cargo.lock", ws.config(), "Cargo.lock file"); + let orig = lock_root.open_ro_shared("Cargo.lock", ws.config(), "Cargo.lock file"); let orig = orig.and_then(|mut f| { let mut s = String::new(); f.read_to_string(&mut s)?; diff --git a/src/tools/cargo/src/cargo/ops/registry/mod.rs b/src/tools/cargo/src/cargo/ops/registry/mod.rs index c82230a16..3fa8fd291 100644 --- a/src/tools/cargo/src/cargo/ops/registry/mod.rs +++ b/src/tools/cargo/src/cargo/ops/registry/mod.rs @@ -22,6 +22,7 @@ use crate::core::SourceId; use crate::sources::source::Source; use crate::sources::{RegistrySource, SourceConfigMap}; use crate::util::auth; +use crate::util::cache_lock::CacheLockMode; use crate::util::config::{Config, PathAndArgs}; use crate::util::errors::CargoResult; use crate::util::network::http::http_handle; @@ -131,7 +132,7 @@ fn registry( } let cfg = { - let _lock = config.acquire_package_cache_lock()?; + let _lock = config.acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?; let mut src = RegistrySource::remote(source_ids.replacement, &HashSet::new(), config)?; // Only update the index if `force_update` is set. if force_update { diff --git a/src/tools/cargo/src/cargo/ops/registry/publish.rs b/src/tools/cargo/src/cargo/ops/registry/publish.rs index a88a0a30f..201907bb2 100644 --- a/src/tools/cargo/src/cargo/ops/registry/publish.rs +++ b/src/tools/cargo/src/cargo/ops/registry/publish.rs @@ -30,6 +30,7 @@ use crate::sources::source::QueryKind; use crate::sources::SourceConfigMap; use crate::sources::CRATES_IO_REGISTRY; use crate::util::auth; +use crate::util::cache_lock::CacheLockMode; use crate::util::config::JobsConfig; use crate::util::Progress; use crate::util::ProgressStyle; @@ -102,7 +103,7 @@ pub fn publish(ws: &Workspace<'_>, opts: &PublishOpts<'_>) -> CargoResult<()> { if allowed_registries.is_empty() { bail!( "`{}` cannot be published.\n\ - `package.publish` is set to `false` or an empty list in Cargo.toml and prevents publishing.", + `package.publish` must be set to `true` or a non-empty list in Cargo.toml to publish.", pkg.name(), ); } else if !allowed_registries.contains(®_name) { @@ -233,7 +234,7 @@ fn wait_for_publish( progress.tick_now(0, max, "")?; let is_available = loop { { - let _lock = config.acquire_package_cache_lock()?; + let _lock = config.acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?; // Force re-fetching the source // // As pulling from a git source is expensive, we track when we've done it within the diff --git a/src/tools/cargo/src/cargo/ops/registry/search.rs b/src/tools/cargo/src/cargo/ops/registry/search.rs index 10b4d600e..0f4649754 100644 --- a/src/tools/cargo/src/cargo/ops/registry/search.rs +++ b/src/tools/cargo/src/cargo/ops/registry/search.rs @@ -3,7 +3,6 @@ //! [1]: https://doc.rust-lang.org/nightly/cargo/reference/registry-web-api.html#search use std::cmp; -use std::iter::repeat; use anyhow::Context as _; use url::Url; @@ -35,7 +34,7 @@ pub fn search( .map(|krate| format!("{} = \"{}\"", krate.name, krate.max_version)) .collect::<Vec<String>>(); - let description_margin = names.iter().map(|s| s.len() + 4).max().unwrap_or_default(); + let description_margin = names.iter().map(|s| s.len()).max().unwrap_or_default() + 4; let description_length = cmp::max(80, 128 - description_margin); @@ -46,34 +45,32 @@ pub fn search( .map(|desc| truncate_with_ellipsis(&desc.replace("\n", " "), description_length)) }); + let mut shell = config.shell(); + let stdout = shell.out(); + let good = style::GOOD.render(); + let reset = anstyle::Reset.render(); + for (name, description) in names.into_iter().zip(descriptions) { let line = match description { - Some(desc) => { - let space = repeat(' ') - .take(description_margin - name.len()) - .collect::<String>(); - name + &space + "# " + &desc - } + Some(desc) => format!("{name: <description_margin$}# {desc}"), None => name, }; let mut fragments = line.split(query).peekable(); while let Some(fragment) = fragments.next() { - let _ = config.shell().write_stdout(fragment, &style::NOP); + let _ = write!(stdout, "{fragment}"); if fragments.peek().is_some() { - let _ = config.shell().write_stdout(query, &style::GOOD); + let _ = write!(stdout, "{good}{query}{reset}"); } } - let _ = config.shell().write_stdout("\n", &style::NOP); + let _ = writeln!(stdout); } let search_max_limit = 100; if total_crates > limit && limit < search_max_limit { - let _ = config.shell().write_stdout( - format_args!( - "... and {} crates more (use --limit N to see more)\n", - total_crates - limit - ), - &style::NOP, + let _ = writeln!( + stdout, + "... and {} crates more (use --limit N to see more)", + total_crates - limit ); } else if total_crates > limit && limit >= search_max_limit { let extra = if source_ids.original.is_crates_io() { @@ -82,9 +79,11 @@ pub fn search( } else { String::new() }; - let _ = config.shell().write_stdout( - format_args!("... and {} crates more{}\n", total_crates - limit, extra), - &style::NOP, + let _ = writeln!( + stdout, + "... and {} crates more{}", + total_crates - limit, + extra ); } diff --git a/src/tools/cargo/src/cargo/ops/resolve.rs b/src/tools/cargo/src/cargo/ops/resolve.rs index 053098d55..8ca72f77c 100644 --- a/src/tools/cargo/src/cargo/ops/resolve.rs +++ b/src/tools/cargo/src/cargo/ops/resolve.rs @@ -61,13 +61,14 @@ use crate::core::resolver::features::{ CliFeatures, FeatureOpts, FeatureResolver, ForceAllTargets, RequestedFeatures, ResolvedFeatures, }; use crate::core::resolver::{ - self, HasDevUnits, Resolve, ResolveOpts, ResolveVersion, VersionPreferences, + self, HasDevUnits, Resolve, ResolveOpts, ResolveVersion, VersionOrdering, VersionPreferences, }; use crate::core::summary::Summary; use crate::core::Feature; use crate::core::{GitReference, PackageId, PackageIdSpec, PackageSet, SourceId, Workspace}; use crate::ops; use crate::sources::PathSource; +use crate::util::cache_lock::CacheLockMode; use crate::util::errors::CargoResult; use crate::util::RustVersion; use crate::util::{profile, CanonicalUrl}; @@ -289,7 +290,9 @@ pub fn resolve_with_previous<'cfg>( ) -> CargoResult<Resolve> { // We only want one Cargo at a time resolving a crate graph since this can // involve a lot of frobbing of the global caches. - let _lock = ws.config().acquire_package_cache_lock()?; + let _lock = ws + .config() + .acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?; // Here we place an artificial limitation that all non-registry sources // cannot be locked at more than one revision. This means that if a Git @@ -318,6 +321,12 @@ pub fn resolve_with_previous<'cfg>( // While registering patches, we will record preferences for particular versions // of various packages. let mut version_prefs = VersionPreferences::default(); + if ws.config().cli_unstable().minimal_versions { + version_prefs.version_ordering(VersionOrdering::MinimumVersionsFirst) + } + if ws.config().cli_unstable().msrv_policy { + version_prefs.max_rust_version(max_rust_version.cloned()); + } // This is a set of PackageIds of `[patch]` entries, and some related locked PackageIds, for // which locking should be avoided (but which will be preferred when searching dependencies, @@ -386,12 +395,8 @@ pub fn resolve_with_previous<'cfg>( }) { Some(id_using_default) => { let id_using_master = id_using_default.with_source_id( - dep.source_id().with_precise( - id_using_default - .source_id() - .precise() - .map(|s| s.to_string()), - ), + dep.source_id() + .with_precise_from(id_using_default.source_id()), ); let mut locked_dep = dep.clone(); @@ -510,7 +515,6 @@ pub fn resolve_with_previous<'cfg>( ws.unstable_features() .require(Feature::public_dependency()) .is_ok(), - max_rust_version, )?; let patches: Vec<_> = registry .patches() @@ -793,7 +797,7 @@ fn master_branch_git_source(id: PackageId, resolve: &Resolve) -> Option<PackageI let new_source = SourceId::for_git(source.url(), GitReference::Branch("master".to_string())) .unwrap() - .with_precise(source.precise().map(|s| s.to_string())); + .with_precise_from(source); return Some(id.with_source_id(new_source)); } } diff --git a/src/tools/cargo/src/cargo/ops/vendor.rs b/src/tools/cargo/src/cargo/ops/vendor.rs index 3ee46db32..cad7fc5d1 100644 --- a/src/tools/cargo/src/cargo/ops/vendor.rs +++ b/src/tools/cargo/src/cargo/ops/vendor.rs @@ -258,7 +258,7 @@ fn sync( } else { // Remove `precise` since that makes the source name very long, // and isn't needed to disambiguate multiple sources. - source_id.with_precise(None).as_url().to_string() + source_id.without_precise().as_url().to_string() }; let source = if source_id.is_crates_io() { diff --git a/src/tools/cargo/src/cargo/sources/config.rs b/src/tools/cargo/src/cargo/sources/config.rs index cc7aa9925..4a0332eca 100644 --- a/src/tools/cargo/src/cargo/sources/config.rs +++ b/src/tools/cargo/src/cargo/sources/config.rs @@ -145,7 +145,7 @@ impl<'cfg> SourceConfigMap<'cfg> { // Attempt to interpret the source name as an alt registry name if let Ok(alt_id) = SourceId::alt_registry(self.config, name) { debug!("following pointer to registry {}", name); - break alt_id.with_precise(id.precise().map(str::to_string)); + break alt_id.with_precise_from(id); } bail!( "could not find a configured source with the \ @@ -163,7 +163,7 @@ impl<'cfg> SourceConfigMap<'cfg> { } None if id == cfg.id => return id.load(self.config, yanked_whitelist), None => { - break cfg.id.with_precise(id.precise().map(|s| s.to_string())); + break cfg.id.with_precise_from(id); } } debug!("following pointer to {}", name); @@ -199,7 +199,7 @@ a lock file compatible with `{orig}` cannot be generated in this situation ); } - if old_src.requires_precise() && id.precise().is_none() { + if old_src.requires_precise() && !id.has_precise() { bail!( "\ the source {orig} requires a lock file to be present first before it can be diff --git a/src/tools/cargo/src/cargo/sources/git/source.rs b/src/tools/cargo/src/cargo/sources/git/source.rs index 7af342f45..a75c1ec6d 100644 --- a/src/tools/cargo/src/cargo/sources/git/source.rs +++ b/src/tools/cargo/src/cargo/sources/git/source.rs @@ -8,6 +8,7 @@ use crate::sources::source::MaybePackage; use crate::sources::source::QueryKind; use crate::sources::source::Source; use crate::sources::PathSource; +use crate::util::cache_lock::CacheLockMode; use crate::util::errors::CargoResult; use crate::util::hex::short_hash; use crate::util::Config; @@ -88,13 +89,7 @@ impl<'cfg> GitSource<'cfg> { let remote = GitRemote::new(source_id.url()); let manifest_reference = source_id.git_reference().unwrap().clone(); - let locked_rev = - match source_id.precise() { - Some(s) => Some(git2::Oid::from_str(s).with_context(|| { - format!("precise value for git is not a git revision: {}", s) - })?), - None => None, - }; + let locked_rev = source_id.precise_git_oid()?; let ident = ident_shallow( &source_id, config @@ -212,7 +207,9 @@ impl<'cfg> Source for GitSource<'cfg> { // Ignore errors creating it, in case this is a read-only filesystem: // perhaps the later operations can succeed anyhow. let _ = git_fs.create_dir(); - let git_path = self.config.assert_package_cache_locked(&git_fs); + let git_path = self + .config + .assert_package_cache_locked(CacheLockMode::DownloadExclusive, &git_fs); // Before getting a checkout, make sure that `<cargo_home>/git` is // marked as excluded from indexing and backups. Older versions of Cargo @@ -287,7 +284,9 @@ impl<'cfg> Source for GitSource<'cfg> { .join(short_id.as_str()); db.copy_to(actual_rev, &checkout_path, self.config)?; - let source_id = self.source_id.with_precise(Some(actual_rev.to_string())); + let source_id = self + .source_id + .with_git_precise(Some(actual_rev.to_string())); let path_source = PathSource::new_recursive(&checkout_path, source_id, self.config); self.path_source = Some(path_source); diff --git a/src/tools/cargo/src/cargo/sources/registry/download.rs b/src/tools/cargo/src/cargo/sources/registry/download.rs index 9bf838625..786432835 100644 --- a/src/tools/cargo/src/cargo/sources/registry/download.rs +++ b/src/tools/cargo/src/cargo/sources/registry/download.rs @@ -12,6 +12,7 @@ use crate::core::PackageId; use crate::sources::registry::MaybeLock; use crate::sources::registry::RegistryConfig; use crate::util::auth; +use crate::util::cache_lock::CacheLockMode; use crate::util::errors::CargoResult; use crate::util::{Config, Filesystem}; use std::fmt::Write as FmtWrite; @@ -38,7 +39,7 @@ pub(super) fn download( registry_config: RegistryConfig, ) -> CargoResult<MaybeLock> { let path = cache_path.join(&pkg.tarball_name()); - let path = config.assert_package_cache_locked(&path); + let path = config.assert_package_cache_locked(CacheLockMode::DownloadExclusive, &path); // Attempt to open a read-only copy first to avoid an exclusive write // lock and also work with read-only filesystems. Note that we check the @@ -117,7 +118,7 @@ pub(super) fn finish_download( cache_path.create_dir()?; let path = cache_path.join(&pkg.tarball_name()); - let path = config.assert_package_cache_locked(&path); + let path = config.assert_package_cache_locked(CacheLockMode::DownloadExclusive, &path); let mut dst = OpenOptions::new() .create(true) .read(true) @@ -144,7 +145,7 @@ pub(super) fn is_crate_downloaded( pkg: PackageId, ) -> bool { let path = cache_path.join(pkg.tarball_name()); - let path = config.assert_package_cache_locked(&path); + let path = config.assert_package_cache_locked(CacheLockMode::DownloadExclusive, &path); if let Ok(meta) = fs::metadata(path) { return meta.len() > 0; } diff --git a/src/tools/cargo/src/cargo/sources/registry/http_remote.rs b/src/tools/cargo/src/cargo/sources/registry/http_remote.rs index 9fe76333b..3d31110c3 100644 --- a/src/tools/cargo/src/cargo/sources/registry/http_remote.rs +++ b/src/tools/cargo/src/cargo/sources/registry/http_remote.rs @@ -4,6 +4,7 @@ use crate::core::{PackageId, SourceId}; use crate::sources::registry::download; use crate::sources::registry::MaybeLock; use crate::sources::registry::{LoadResponse, RegistryConfig, RegistryData}; +use crate::util::cache_lock::CacheLockMode; use crate::util::errors::{CargoResult, HttpNotSuccessful}; use crate::util::network::http::http_handle; use crate::util::network::retry::{Retry, RetryResult}; @@ -461,7 +462,8 @@ impl<'cfg> RegistryData for HttpRegistry<'cfg> { } fn assert_index_locked<'a>(&self, path: &'a Filesystem) -> &'a Path { - self.config.assert_package_cache_locked(path) + self.config + .assert_package_cache_locked(CacheLockMode::DownloadExclusive, path) } fn is_updated(&self) -> bool { @@ -744,6 +746,7 @@ impl<'cfg> RegistryData for HttpRegistry<'cfg> { Poll::Ready(cfg) => break cfg.to_owned(), } }; + download::download( &self.cache_path, &self.config, diff --git a/src/tools/cargo/src/cargo/sources/registry/index.rs b/src/tools/cargo/src/cargo/sources/registry/index.rs index ca1cf4069..00f21d669 100644 --- a/src/tools/cargo/src/cargo/sources/registry/index.rs +++ b/src/tools/cargo/src/cargo/sources/registry/index.rs @@ -89,6 +89,7 @@ use crate::core::dependency::{Artifact, DepKind}; use crate::core::Dependency; use crate::core::{PackageId, SourceId, Summary}; use crate::sources::registry::{LoadResponse, RegistryData}; +use crate::util::cache_lock::CacheLockMode; use crate::util::interning::InternedString; use crate::util::IntoUrl; use crate::util::{internal, CargoResult, Config, Filesystem, OptVersionReq, RustVersion}; @@ -98,7 +99,7 @@ use semver::Version; use serde::Deserialize; use std::borrow::Cow; use std::collections::BTreeMap; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use std::fs; use std::io::ErrorKind; use std::path::Path; @@ -437,11 +438,9 @@ impl<'cfg> RegistryIndex<'cfg> { /// checking the integrity of a downloaded package matching the checksum in /// the index file, aka [`IndexSummary`]. pub fn hash(&mut self, pkg: PackageId, load: &mut dyn RegistryData) -> Poll<CargoResult<&str>> { - let req = OptVersionReq::exact(pkg.version()); + let req = OptVersionReq::lock_to_exact(pkg.version()); let summary = self.summaries(pkg.name(), &req, load)?; - let summary = ready!(summary) - .filter(|s| s.package_id().version() == pkg.version()) - .next(); + let summary = ready!(summary).next(); Poll::Ready(Ok(summary .ok_or_else(|| internal(format!("no hash listed for {}", pkg)))? .as_summary() @@ -574,8 +573,7 @@ impl<'cfg> RegistryIndex<'cfg> { name: InternedString, req: &OptVersionReq, load: &mut dyn RegistryData, - yanked_whitelist: &HashSet<PackageId>, - f: &mut dyn FnMut(Summary), + f: &mut dyn FnMut(IndexSummary), ) -> Poll<CargoResult<()>> { if self.config.offline() { // This should only return `Poll::Ready(Ok(()))` if there is at least 1 match. @@ -592,31 +590,15 @@ impl<'cfg> RegistryIndex<'cfg> { let callback = &mut |s: IndexSummary| { if !s.is_offline() { called = true; - f(s.into_summary()); + f(s); } }; - ready!(self.query_inner_with_online( - name, - req, - load, - yanked_whitelist, - callback, - false - )?); + ready!(self.query_inner_with_online(name, req, load, callback, false)?); if called { return Poll::Ready(Ok(())); } } - self.query_inner_with_online( - name, - req, - load, - yanked_whitelist, - &mut |s| { - f(s.into_summary()); - }, - true, - ) + self.query_inner_with_online(name, req, load, f, true) } /// Inner implementation of [`Self::query_inner`]. Returns the number of @@ -628,15 +610,10 @@ impl<'cfg> RegistryIndex<'cfg> { name: InternedString, req: &OptVersionReq, load: &mut dyn RegistryData, - yanked_whitelist: &HashSet<PackageId>, f: &mut dyn FnMut(IndexSummary), online: bool, ) -> Poll<CargoResult<()>> { - let source_id = self.source_id; - - let summaries = ready!(self.summaries(name, req, load))?; - - let summaries = summaries + ready!(self.summaries(name, &req, load))? // First filter summaries for `--offline`. If we're online then // everything is a candidate, otherwise if we're offline we're only // going to consider candidates which are actually present on disk. @@ -654,41 +631,7 @@ impl<'cfg> RegistryIndex<'cfg> { IndexSummary::Offline(s.as_summary().clone()) } }) - // Next filter out all yanked packages. Some yanked packages may - // leak through if they're in a whitelist (aka if they were - // previously in `Cargo.lock` - .filter(|s| !s.is_yanked() || yanked_whitelist.contains(&s.package_id())); - - // Handle `cargo update --precise` here. - let precise = source_id.precise_registry_version(name.as_str()); - let summaries = summaries.filter(|s| match &precise { - Some((current, requested)) => { - 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 - // specified, then ignore it. - let s_vers = s.package_id().version(); - match (s_vers.build.is_empty(), requested.build.is_empty()) { - (true, true) => s_vers == requested, - (true, false) => false, - (false, true) => { - // Strip out the metadata. - s_vers.major == requested.major - && s_vers.minor == requested.minor - && s_vers.patch == requested.patch - && s_vers.pre == requested.pre - } - (false, false) => s_vers == requested, - } - } else { - true - } - } - None => true, - }); - - summaries.for_each(f); + .for_each(f); Poll::Ready(Ok(())) } @@ -698,10 +641,8 @@ impl<'cfg> RegistryIndex<'cfg> { pkg: PackageId, load: &mut dyn RegistryData, ) -> Poll<CargoResult<bool>> { - let req = OptVersionReq::exact(pkg.version()); - let found = ready!(self.summaries(pkg.name(), &req, load))? - .filter(|s| s.package_id().version() == pkg.version()) - .any(|s| s.is_yanked()); + let req = OptVersionReq::lock_to_exact(pkg.version()); + let found = ready!(self.summaries(pkg.name(), &req, load))?.any(|s| s.is_yanked()); Poll::Ready(Ok(found)) } } @@ -823,7 +764,7 @@ impl Summaries { // something in case of error. if paths::create_dir_all(cache_path.parent().unwrap()).is_ok() { let path = Filesystem::new(cache_path.clone()); - config.assert_package_cache_locked(&path); + config.assert_package_cache_locked(CacheLockMode::DownloadExclusive, &path); if let Err(e) = fs::write(cache_path, &cache_bytes) { tracing::info!("failed to write cache: {}", e); } @@ -994,7 +935,7 @@ impl IndexSummary { } = serde_json::from_slice(line)?; let v = v.unwrap_or(1); tracing::trace!("json parsed registry {}/{}", name, vers); - let pkgid = PackageId::new(name, &vers, source_id)?; + let pkgid = PackageId::pure(name.into(), vers.clone(), source_id); let deps = deps .into_iter() .map(|dep| dep.into_dep(source_id)) diff --git a/src/tools/cargo/src/cargo/sources/registry/mod.rs b/src/tools/cargo/src/cargo/sources/registry/mod.rs index fb8f79817..7ee461edd 100644 --- a/src/tools/cargo/src/cargo/sources/registry/mod.rs +++ b/src/tools/cargo/src/cargo/sources/registry/mod.rs @@ -206,6 +206,7 @@ use crate::sources::source::MaybePackage; use crate::sources::source::QueryKind; use crate::sources::source::Source; use crate::sources::PathSource; +use crate::util::cache_lock::CacheLockMode; use crate::util::hex; use crate::util::interning::InternedString; use crate::util::network::PollExt; @@ -581,7 +582,9 @@ impl<'cfg> RegistrySource<'cfg> { let package_dir = format!("{}-{}", pkg.name(), pkg.version()); let dst = self.src_path.join(&package_dir); let path = dst.join(PACKAGE_SOURCE_LOCK); - let path = self.config.assert_package_cache_locked(&path); + let path = self + .config + .assert_package_cache_locked(CacheLockMode::DownloadExclusive, &path); let unpack_dir = path.parent().unwrap(); match fs::read_to_string(path) { Ok(ok) => match serde_json::from_str::<LockMetadata>(&ok) { @@ -709,26 +712,33 @@ impl<'cfg> Source for RegistrySource<'cfg> { kind: QueryKind, f: &mut dyn FnMut(Summary), ) -> Poll<CargoResult<()>> { - // If this is a precise dependency, then it came from a lock file and in + let mut req = dep.version_req().clone(); + + // Handle `cargo update --precise` here. + if let Some((_, requested)) = self + .source_id + .precise_registry_version(dep.package_name().as_str()) + .filter(|(c, _)| req.matches(c)) + { + req.update_precise(&requested); + } + + // If this is a locked dependency, then it came from a lock file and in // theory the registry is known to contain this version. If, however, we // come back with no summaries, then our registry may need to be // updated, so we fall back to performing a lazy update. - if kind == QueryKind::Exact && dep.source_id().precise().is_some() && !self.ops.is_updated() - { + if kind == QueryKind::Exact && req.is_locked() && !self.ops.is_updated() { debug!("attempting query without update"); let mut called = false; - ready!(self.index.query_inner( - dep.package_name(), - dep.version_req(), - &mut *self.ops, - &self.yanked_whitelist, - &mut |s| { - if dep.matches(&s) { + ready!(self + .index + .query_inner(dep.package_name(), &req, &mut *self.ops, &mut |s| { + if dep.matches(s.as_summary()) { + // We are looking for a package from a lock file so we do not care about yank called = true; - f(s); + f(s.into_summary()); } - }, - ))?; + },))?; if called { Poll::Ready(Ok(())) } else { @@ -738,22 +748,23 @@ impl<'cfg> Source for RegistrySource<'cfg> { } } else { let mut called = false; - ready!(self.index.query_inner( - dep.package_name(), - dep.version_req(), - &mut *self.ops, - &self.yanked_whitelist, - &mut |s| { + ready!(self + .index + .query_inner(dep.package_name(), &req, &mut *self.ops, &mut |s| { let matched = match kind { - QueryKind::Exact => dep.matches(&s), + QueryKind::Exact => dep.matches(s.as_summary()), QueryKind::Fuzzy => true, }; - if matched { - f(s); + // Next filter out all yanked packages. Some yanked packages may + // leak through if they're in a whitelist (aka if they were + // previously in `Cargo.lock` + if matched + && (!s.is_yanked() || self.yanked_whitelist.contains(&s.package_id())) + { + f(s.into_summary()); called = true; } - } - ))?; + }))?; if called { return Poll::Ready(Ok(())); } @@ -775,13 +786,9 @@ impl<'cfg> Source for RegistrySource<'cfg> { } any_pending |= self .index - .query_inner( - name_permutation, - dep.version_req(), - &mut *self.ops, - &self.yanked_whitelist, - f, - )? + .query_inner(name_permutation, &req, &mut *self.ops, &mut |s| { + f(s.into_summary()); + })? .is_pending(); } } diff --git a/src/tools/cargo/src/cargo/sources/registry/remote.rs b/src/tools/cargo/src/cargo/sources/registry/remote.rs index 39eb14dc0..ba171eac3 100644 --- a/src/tools/cargo/src/cargo/sources/registry/remote.rs +++ b/src/tools/cargo/src/cargo/sources/registry/remote.rs @@ -6,6 +6,7 @@ use crate::sources::git::fetch::RemoteKind; use crate::sources::registry::download; use crate::sources::registry::MaybeLock; use crate::sources::registry::{LoadResponse, RegistryConfig, RegistryData}; +use crate::util::cache_lock::CacheLockMode; use crate::util::errors::CargoResult; use crate::util::interning::InternedString; use crate::util::{Config, Filesystem}; @@ -104,7 +105,9 @@ impl<'cfg> RemoteRegistry<'cfg> { fn repo(&self) -> CargoResult<&git2::Repository> { self.repo.try_borrow_with(|| { trace!("acquiring registry index lock"); - let path = self.config.assert_package_cache_locked(&self.index_path); + let path = self + .config + .assert_package_cache_locked(CacheLockMode::DownloadExclusive, &self.index_path); match git2::Repository::open(&path) { Ok(repo) => Ok(repo), @@ -216,7 +219,8 @@ impl<'cfg> RegistryData for RemoteRegistry<'cfg> { } fn assert_index_locked<'a>(&self, path: &'a Filesystem) -> &'a Path { - self.config.assert_package_cache_locked(path) + self.config + .assert_package_cache_locked(CacheLockMode::DownloadExclusive, path) } /// Read the general concept for `load()` on [`RegistryData::load`]. @@ -302,7 +306,8 @@ impl<'cfg> RegistryData for RemoteRegistry<'cfg> { fn config(&mut self) -> Poll<CargoResult<Option<RegistryConfig>>> { debug!("loading config"); self.prepare()?; - self.config.assert_package_cache_locked(&self.index_path); + self.config + .assert_package_cache_locked(CacheLockMode::DownloadExclusive, &self.index_path); match ready!(self.load(Path::new(""), Path::new(RegistryConfig::NAME), None)?) { LoadResponse::Data { raw_data, .. } => { trace!("config loaded"); @@ -346,7 +351,9 @@ impl<'cfg> RegistryData for RemoteRegistry<'cfg> { self.head.set(None); *self.tree.borrow_mut() = None; self.current_sha.set(None); - let _path = self.config.assert_package_cache_locked(&self.index_path); + let _path = self + .config + .assert_package_cache_locked(CacheLockMode::DownloadExclusive, &self.index_path); if !self.quiet { self.config .shell() diff --git a/src/tools/cargo/src/cargo/util/auth/mod.rs b/src/tools/cargo/src/cargo/util/auth/mod.rs index ea82dce0c..c2f818645 100644 --- a/src/tools/cargo/src/cargo/util/auth/mod.rs +++ b/src/tools/cargo/src/cargo/util/auth/mod.rs @@ -529,9 +529,15 @@ fn credential_action( } "cargo:paseto" => bail!("cargo:paseto requires -Zasymmetric-token"), "cargo:token-from-stdout" => Box::new(BasicProcessCredential {}), + #[cfg(windows)] "cargo:wincred" => Box::new(cargo_credential_wincred::WindowsCredential {}), + #[cfg(target_os = "macos")] "cargo:macos-keychain" => Box::new(cargo_credential_macos_keychain::MacKeychain {}), + #[cfg(target_os = "linux")] "cargo:libsecret" => Box::new(cargo_credential_libsecret::LibSecretCredential {}), + name if BUILT_IN_PROVIDERS.contains(&name) => { + Box::new(cargo_credential::UnsupportedCredential {}) + } process => Box::new(CredentialProcessCredential::new(process)), }; config.shell().verbose(|c| { diff --git a/src/tools/cargo/src/cargo/util/cache_lock.rs b/src/tools/cargo/src/cargo/util/cache_lock.rs new file mode 100644 index 000000000..ca9e8d1b0 --- /dev/null +++ b/src/tools/cargo/src/cargo/util/cache_lock.rs @@ -0,0 +1,549 @@ +//! Support for locking the package and index caches. +//! +//! This implements locking on the package and index caches (source files, +//! `.crate` files, and index caches) to coordinate when multiple cargos are +//! running at the same time. +//! +//! ## Usage +//! +//! There is a global [`CacheLocker`] held inside cargo's venerable +//! [`Config`]. The `CacheLocker` manages creating and tracking the locks +//! being held. There are methods on `Config` for managing the locks: +//! +//! - [`Config::acquire_package_cache_lock`] --- Acquires a lock. May block if +//! another process holds a lock. +//! - [`Config::try_acquire_package_cache_lock`] --- Acquires a lock, returning +//! immediately if it would block. +//! - [`Config::assert_package_cache_locked`] --- This is used to ensure the +//! proper lock is being held. +//! +//! Lower-level code that accesses the package cache typically just use +//! `assert_package_cache_locked` to ensure that the correct lock is being +//! held. Higher-level code is responsible for acquiring the appropriate lock, +//! and holding it during the duration that it is performing its operation. +//! +//! ## Types of locking +//! +//! There are three styles of locks: +//! +//! * [`CacheLockMode::DownloadExclusive`] -- This is an exclusive lock +//! acquired while downloading packages and doing resolution. +//! * [`CacheLockMode::Shared`] -- This is a shared lock acquired while a +//! build is running. In other words, whenever cargo just needs to read from +//! the cache, it should hold this lock. This is here to ensure that no +//! cargos are trying to read the source caches when cache garbage +//! collection runs. +//! * [`CacheLockMode::MutateExclusive`] -- This is an exclusive lock acquired +//! whenever needing to modify existing source files (for example, with +//! cache garbage collection). This is acquired to make sure that no other +//! cargo is reading from the cache. +//! +//! Importantly, a `DownloadExclusive` lock does *not* interfere with a +//! `Shared` lock. The download process generally does not modify source files +//! (it only adds new ones), so other cargos should be able to safely proceed +//! in reading source files[^1]. +//! +//! See the [`CacheLockMode`] enum docs for more details on when the different +//! modes should be used. +//! +//! ## Locking implementation details +//! +//! This is implemented by two separate lock files, the "download" one and the +//! "mutate" one. The `MutateExclusive` lock acquired both the "mutate" and +//! "download" locks. The `Shared` lock acquires the "mutate" lock in share +//! mode. +//! +//! An important rule is that `MutateExclusive` acquires the locks in the +//! order "mutate" first and then the "download". That helps prevent +//! deadlocks. It is not allowed for a cargo to first acquire a +//! `DownloadExclusive` lock and then a `Shared` lock because that would open +//! it up for deadlock. +//! +//! Another rule is that there should be only one [`CacheLocker`] per process +//! to uphold the ordering rules. You could in theory have multiple if you +//! could ensure that other threads would make progress and drop a lock, but +//! cargo is not architected that way. +//! +//! It is safe to recursively acquire a lock as many times as you want. +//! +//! ## Interaction with older cargos +//! +//! Before version 1.74, cargo only acquired the `DownloadExclusive` lock when +//! downloading and doing resolution. Newer cargos that acquire +//! `MutateExclusive` should still correctly block when an old cargo is +//! downloading (because it also acquires `DownloadExclusive`), but they do +//! not properly coordinate when an old cargo is in the build phase (because +//! it holds no locks). This isn't expected to be much of a problem because +//! the intended use of mutating the cache is only to delete old contents +//! which aren't currently being used. It is possible for there to be a +//! conflict, particularly if the user manually deletes the entire cache, but +//! it is not expected for this scenario to happen too often, and the only +//! consequence is that one side or the other encounters an error and needs to +//! retry. +//! +//! [^1]: A minor caveat is that downloads will delete an existing `src` +//! directory if it was extracted via an old cargo. See +//! [`crate::sources::registry::RegistrySource::unpack_package`]. This +//! should probably be fixed, but is unlikely to be a problem if the user is +//! only using versions of cargo with the same deletion logic. + +use super::FileLock; +use crate::CargoResult; +use crate::Config; +use anyhow::Context; +use std::cell::RefCell; +use std::io; + +/// The style of lock to acquire. +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum CacheLockMode { + /// A `DownloadExclusive` lock ensures that only one cargo is doing + /// resolution and downloading new packages. + /// + /// You should use this when downloading new packages or doing resolution. + /// + /// If another cargo has a `MutateExclusive` lock, then an attempt to get + /// a `DownloadExclusive` lock will block. + /// + /// If another cargo has a `Shared` lock, then both can operate + /// concurrently. + DownloadExclusive, + /// A `Shared` lock allows multiple cargos to read from the source files. + /// + /// You should use this when cargo is reading source files from the + /// package cache. This is typically done during the build phase, since + /// cargo only needs to read files during that time. This allows multiple + /// cargo processes to build concurrently without interfering with one + /// another, while guarding against other cargos using `MutateExclusive`. + /// + /// If another cargo has a `MutateExclusive` lock, then an attempt to get + /// a `Shared` will block. + /// + /// If another cargo has a `DownloadExclusive` lock, then they both can + /// operate concurrently under the assumption that downloading does not + /// modify existing source files. + Shared, + /// A `MutateExclusive` lock ensures no other cargo is reading or writing + /// from the package caches. + /// + /// You should use this when modifying existing files in the package + /// cache. For example, things like garbage collection want to avoid + /// deleting files while other cargos are trying to read (`Shared`) or + /// resolve or download (`DownloadExclusive`). + /// + /// If another cargo has a `DownloadExclusive` or `Shared` lock, then this + /// will block until they all release their locks. + MutateExclusive, +} + +/// Whether or not a lock attempt should block. +#[derive(Copy, Clone)] +enum BlockingMode { + Blocking, + NonBlocking, +} + +use BlockingMode::*; + +/// Whether or not a lock attempt blocked or succeeded. +#[derive(PartialEq, Copy, Clone)] +#[must_use] +enum LockingResult { + LockAcquired, + WouldBlock, +} + +use LockingResult::*; + +/// A file lock, with a counter to assist with recursive locking. +#[derive(Debug)] +struct RecursiveLock { + /// The file lock. + /// + /// An important note is that locks can be `None` even when they are held. + /// This can happen on things like old NFS mounts where locking isn't + /// supported. We otherwise pretend we have a lock via the lock count. See + /// [`FileLock`] for more detail on that. + lock: Option<FileLock>, + /// Number locks held, to support recursive locking. + count: u32, + /// If this is `true`, it is an exclusive lock, otherwise it is shared. + is_exclusive: bool, + /// The filename of the lock. + filename: &'static str, +} + +impl RecursiveLock { + fn new(filename: &'static str) -> RecursiveLock { + RecursiveLock { + lock: None, + count: 0, + is_exclusive: false, + filename, + } + } + + /// Low-level lock count increment routine. + fn increment(&mut self) { + self.count = self.count.checked_add(1).unwrap(); + } + + /// Unlocks a previously acquired lock. + fn decrement(&mut self) { + let new_cnt = self.count.checked_sub(1).unwrap(); + self.count = new_cnt; + if new_cnt == 0 { + // This will drop, releasing the lock. + self.lock = None; + } + } + + /// Acquires a shared lock. + fn lock_shared( + &mut self, + config: &Config, + description: &'static str, + blocking: BlockingMode, + ) -> LockingResult { + match blocking { + Blocking => { + self.lock_shared_blocking(config, description); + LockAcquired + } + NonBlocking => self.lock_shared_nonblocking(config), + } + } + + /// Acquires a shared lock, blocking if held by another locker. + fn lock_shared_blocking(&mut self, config: &Config, description: &'static str) { + if self.count == 0 { + self.is_exclusive = false; + self.lock = + match config + .home() + .open_ro_shared_create(self.filename, config, description) + { + Ok(lock) => Some(lock), + Err(e) => { + // There is no error here because locking is mostly a + // best-effort attempt. If cargo home is read-only, we don't + // want to fail just because we couldn't create the lock file. + tracing::warn!("failed to acquire cache lock {}: {e:?}", self.filename); + None + } + }; + } + self.increment(); + } + + /// Acquires a shared lock, returns [`WouldBlock`] if held by another locker. + fn lock_shared_nonblocking(&mut self, config: &Config) -> LockingResult { + if self.count == 0 { + self.is_exclusive = false; + self.lock = match config.home().try_open_ro_shared_create(self.filename) { + Ok(Some(lock)) => Some(lock), + Ok(None) => { + return WouldBlock; + } + Err(e) => { + // Pretend that the lock was acquired (see lock_shared_blocking). + tracing::warn!("failed to acquire cache lock {}: {e:?}", self.filename); + None + } + }; + } + self.increment(); + LockAcquired + } + + /// Acquires an exclusive lock. + fn lock_exclusive( + &mut self, + config: &Config, + description: &'static str, + blocking: BlockingMode, + ) -> CargoResult<LockingResult> { + if self.count > 0 && !self.is_exclusive { + // Lock upgrades are dicey. It might be possible to support + // this but would take a bit of work, and so far it isn't + // needed. + panic!("lock upgrade from shared to exclusive not supported"); + } + match blocking { + Blocking => { + self.lock_exclusive_blocking(config, description)?; + Ok(LockAcquired) + } + NonBlocking => self.lock_exclusive_nonblocking(config), + } + } + + /// Acquires an exclusive lock, blocking if held by another locker. + fn lock_exclusive_blocking( + &mut self, + config: &Config, + description: &'static str, + ) -> CargoResult<()> { + if self.count == 0 { + self.is_exclusive = true; + match config + .home() + .open_rw_exclusive_create(self.filename, config, description) + { + Ok(lock) => self.lock = Some(lock), + Err(e) => { + if maybe_readonly(&e) { + // This is a best-effort attempt to at least try to + // acquire some sort of lock. This can help in the + // situation where this cargo only has read-only access, + // but maybe some other cargo has read-write. This will at + // least attempt to coordinate with it. + // + // We don't want to fail on a read-only mount because + // cargo grabs an exclusive lock in situations where it + // may only be reading from the package cache. In that + // case, cargo isn't writing anything, and we don't want + // to fail on that. + self.lock_shared_blocking(config, description); + // This has to pretend it is exclusive for recursive locks to work. + self.is_exclusive = true; + return Ok(()); + } else { + return Err(e).with_context(|| "failed to acquire package cache lock"); + } + } + } + } + self.increment(); + Ok(()) + } + + /// Acquires an exclusive lock, returns [`WouldBlock`] if held by another locker. + fn lock_exclusive_nonblocking(&mut self, config: &Config) -> CargoResult<LockingResult> { + if self.count == 0 { + self.is_exclusive = true; + match config.home().try_open_rw_exclusive_create(self.filename) { + Ok(Some(lock)) => self.lock = Some(lock), + Ok(None) => return Ok(WouldBlock), + Err(e) => { + if maybe_readonly(&e) { + let result = self.lock_shared_nonblocking(config); + // This has to pretend it is exclusive for recursive locks to work. + self.is_exclusive = true; + return Ok(result); + } else { + return Err(e).with_context(|| "failed to acquire package cache lock"); + } + } + } + } + self.increment(); + Ok(LockAcquired) + } +} + +/// The state of the [`CacheLocker`]. +#[derive(Debug)] +struct CacheState { + /// The cache lock guards the package cache used for download and + /// resolution (append operations that should not interfere with reading + /// from existing src files). + cache_lock: RecursiveLock, + /// The mutate lock is used to either guard the entire package cache for + /// destructive modifications (in exclusive mode), or for reading the + /// package cache src files (in shared mode). + /// + /// Note that [`CacheLockMode::MutateExclusive`] holds both + /// [`CacheState::mutate_lock`] and [`CacheState::cache_lock`]. + mutate_lock: RecursiveLock, +} + +impl CacheState { + fn lock( + &mut self, + config: &Config, + mode: CacheLockMode, + blocking: BlockingMode, + ) -> CargoResult<LockingResult> { + use CacheLockMode::*; + if mode == Shared && self.cache_lock.count > 0 && self.mutate_lock.count == 0 { + // Shared lock, when a DownloadExclusive is held. + // + // This isn't supported because it could cause a deadlock. If + // one cargo is attempting to acquire a MutateExclusive lock, + // and acquires the mutate lock, but is blocked on the + // download lock, and the cargo that holds the download lock + // attempts to get a shared lock, they would end up blocking + // each other. + panic!("shared lock while holding download lock is not allowed"); + } + match mode { + Shared => { + if self.mutate_lock.lock_shared(config, SHARED_DESCR, blocking) == WouldBlock { + return Ok(WouldBlock); + } + } + DownloadExclusive => { + if self + .cache_lock + .lock_exclusive(config, DOWNLOAD_EXCLUSIVE_DESCR, blocking)? + == WouldBlock + { + return Ok(WouldBlock); + } + } + MutateExclusive => { + if self + .mutate_lock + .lock_exclusive(config, MUTATE_EXCLUSIVE_DESCR, blocking)? + == WouldBlock + { + return Ok(WouldBlock); + } + + // Part of the contract of MutateExclusive is that it doesn't + // allow any processes to have a lock on the package cache, so + // this acquires both locks. + match self + .cache_lock + .lock_exclusive(config, DOWNLOAD_EXCLUSIVE_DESCR, blocking) + { + Ok(LockAcquired) => {} + Ok(WouldBlock) => return Ok(WouldBlock), + Err(e) => { + self.mutate_lock.decrement(); + return Err(e); + } + } + } + } + Ok(LockAcquired) + } +} + +/// A held lock guard. +/// +/// When this is dropped, the lock will be released. +#[must_use] +pub struct CacheLock<'lock> { + mode: CacheLockMode, + locker: &'lock CacheLocker, +} + +impl Drop for CacheLock<'_> { + fn drop(&mut self) { + use CacheLockMode::*; + let mut state = self.locker.state.borrow_mut(); + match self.mode { + Shared => { + state.mutate_lock.decrement(); + } + DownloadExclusive => { + state.cache_lock.decrement(); + } + MutateExclusive => { + state.cache_lock.decrement(); + state.mutate_lock.decrement(); + } + } + } +} + +/// The filename for the [`CacheLockMode::DownloadExclusive`] lock. +const CACHE_LOCK_NAME: &str = ".package-cache"; +/// The filename for the [`CacheLockMode::MutateExclusive`] and +/// [`CacheLockMode::Shared`] lock. +const MUTATE_NAME: &str = ".package-cache-mutate"; + +// Descriptions that are displayed in the "Blocking" message shown to the user. +const SHARED_DESCR: &str = "shared package cache"; +const DOWNLOAD_EXCLUSIVE_DESCR: &str = "package cache"; +const MUTATE_EXCLUSIVE_DESCR: &str = "package cache mutation"; + +/// A locker that can be used to acquire locks. +/// +/// See the [`crate::util::cache_lock`] module documentation for an overview +/// of how cache locking works. +#[derive(Debug)] +pub struct CacheLocker { + /// The state of the locker. + /// + /// [`CacheLocker`] uses interior mutability because it is stuffed inside + /// the global `Config`, which does not allow mutation. + state: RefCell<CacheState>, +} + +impl CacheLocker { + /// Creates a new `CacheLocker`. + pub fn new() -> CacheLocker { + CacheLocker { + state: RefCell::new(CacheState { + cache_lock: RecursiveLock::new(CACHE_LOCK_NAME), + mutate_lock: RecursiveLock::new(MUTATE_NAME), + }), + } + } + + /// Acquires a lock with the given mode, possibly blocking if another + /// cargo is holding the lock. + pub fn lock(&self, config: &Config, mode: CacheLockMode) -> CargoResult<CacheLock<'_>> { + let mut state = self.state.borrow_mut(); + let _ = state.lock(config, mode, Blocking)?; + Ok(CacheLock { mode, locker: self }) + } + + /// Acquires a lock with the given mode, returning `None` if another cargo + /// is holding the lock. + pub fn try_lock( + &self, + config: &Config, + mode: CacheLockMode, + ) -> CargoResult<Option<CacheLock<'_>>> { + let mut state = self.state.borrow_mut(); + if state.lock(config, mode, NonBlocking)? == LockAcquired { + Ok(Some(CacheLock { mode, locker: self })) + } else { + Ok(None) + } + } + + /// Returns whether or not a lock is held for the given mode in this locker. + /// + /// This does not tell you whether or not it is locked in some other + /// locker (such as in another process). + /// + /// Note that `Shared` will return true if a `MutateExclusive` lock is + /// held, since `MutateExclusive` is just an upgraded `Shared`. Likewise, + /// `DownlaodExclusive` will return true if a `MutateExclusive` lock is + /// held since they overlap. + pub fn is_locked(&self, mode: CacheLockMode) -> bool { + let state = self.state.borrow(); + match ( + mode, + state.cache_lock.count, + state.mutate_lock.count, + state.mutate_lock.is_exclusive, + ) { + (CacheLockMode::Shared, _, 1.., _) => true, + (CacheLockMode::MutateExclusive, _, 1.., true) => true, + (CacheLockMode::DownloadExclusive, 1.., _, _) => true, + _ => false, + } + } +} + +/// Returns whether or not the error appears to be from a read-only filesystem. +fn maybe_readonly(err: &anyhow::Error) -> bool { + err.chain().any(|err| { + if let Some(io) = err.downcast_ref::<io::Error>() { + if io.kind() == io::ErrorKind::PermissionDenied { + return true; + } + + #[cfg(unix)] + return io.raw_os_error() == Some(libc::EROFS); + } + + false + }) +} diff --git a/src/tools/cargo/src/cargo/util/command_prelude.rs b/src/tools/cargo/src/cargo/util/command_prelude.rs index bd8889bef..3888b80c4 100644 --- a/src/tools/cargo/src/cargo/util/command_prelude.rs +++ b/src/tools/cargo/src/cargo/util/command_prelude.rs @@ -6,9 +6,8 @@ use crate::ops::{CompileFilter, CompileOptions, NewOptions, Packages, VersionCon use crate::util::important_paths::find_root_manifest_for_wd; use crate::util::interning::InternedString; use crate::util::is_rustup; -use crate::util::restricted_names::is_glob_pattern; -use crate::util::toml::{StringOrVec, TomlProfile}; -use crate::util::validate_package_name; +use crate::util::restricted_names; +use crate::util::toml::schema::StringOrVec; use crate::util::{ print_available_benches, print_available_binaries, print_available_examples, print_available_packages, print_available_tests, @@ -64,9 +63,19 @@ pub trait CommandExt: Sized { all: &'static str, exclude: &'static str, ) -> Self { + let unsupported_short_arg = { + let value_parser = UnknownArgumentValueParser::suggest_arg("--exclude"); + Arg::new("unsupported-short-exclude-flag") + .help("") + .short('x') + .value_parser(value_parser) + .action(ArgAction::SetTrue) + .hide(true) + }; self.arg_package_spec_simple(package) ._arg(flag("workspace", all).help_heading(heading::PACKAGE_SELECTION)) ._arg(multi_opt("exclude", "SPEC", exclude).help_heading(heading::PACKAGE_SELECTION)) + ._arg(unsupported_short_arg) } fn arg_package_spec_simple(self, package: &'static str) -> Self { @@ -232,10 +241,20 @@ pub trait CommandExt: Sized { } fn arg_target_triple(self, target: &'static str) -> Self { + let unsupported_short_arg = { + let value_parser = UnknownArgumentValueParser::suggest_arg("--target"); + Arg::new("unsupported-short-target-flag") + .help("") + .short('t') + .value_parser(value_parser) + .action(ArgAction::SetTrue) + .hide(true) + }; self._arg( optional_multi_opt("target", "TRIPLE", target) .help_heading(heading::COMPILATION_OPTIONS), ) + ._arg(unsupported_short_arg) } fn arg_target_dir(self) -> Self { @@ -247,6 +266,20 @@ pub trait CommandExt: Sized { } fn arg_manifest_path(self) -> Self { + // We use `--manifest-path` instead of `--path`. + let unsupported_path_arg = { + let value_parser = UnknownArgumentValueParser::suggest_arg("--manifest-path"); + flag("unsupported-path-flag", "") + .long("path") + .value_parser(value_parser) + .hide(true) + }; + self.arg_manifest_path_without_unsupported_path_tip() + ._arg(unsupported_path_arg) + } + + // `cargo add` has a `--path` flag to install a crate from a local path. + fn arg_manifest_path_without_unsupported_path_tip(self) -> Self { self._arg( opt("manifest-path", "Path to Cargo.toml") .value_name("PATH") @@ -338,7 +371,7 @@ pub trait CommandExt: Sized { .value_parser(value_parser) .hide(true) }; - self._arg(flag("quiet", "Do not print cargo log messages").short('q')) + self.arg_quiet_without_unknown_silent_arg_tip() ._arg(unsupported_silent_arg) } @@ -357,6 +390,27 @@ pub trait CommandExt: Sized { .help_heading(heading::COMPILATION_OPTIONS), ) } + + fn arg_out_dir(self) -> Self { + let unsupported_short_arg = { + let value_parser = UnknownArgumentValueParser::suggest_arg("--out-dir"); + Arg::new("unsupported-short-out-dir-flag") + .help("") + .short('O') + .value_parser(value_parser) + .action(ArgAction::SetTrue) + .hide(true) + }; + self._arg( + opt( + "out-dir", + "Copy final artifacts to this directory (unstable)", + ) + .value_name("PATH") + .help_heading(heading::COMPILATION_OPTIONS), + ) + ._arg(unsupported_short_arg) + } } impl CommandExt for Command { @@ -552,7 +606,7 @@ Run `{cmd}` to see possible targets." bail!("profile `doc` is reserved and not allowed to be explicitly specified") } (_, _, Some(name)) => { - TomlProfile::validate_name(name)?; + restricted_names::validate_profile_name(name)?; name } }; @@ -746,7 +800,7 @@ Run `{cmd}` to see possible targets." ) -> CargoResult<CompileOptions> { let mut compile_opts = self.compile_options(config, mode, workspace, profile_checking)?; let spec = self._values_of("package"); - if spec.iter().any(is_glob_pattern) { + if spec.iter().any(restricted_names::is_glob_pattern) { anyhow::bail!("Glob patterns on package selection are not supported.") } compile_opts.spec = Packages::Packages(spec); @@ -780,7 +834,7 @@ Run `{cmd}` to see possible targets." (None, None) => config.default_registry()?.map(RegistryOrIndex::Registry), (None, Some(i)) => Some(RegistryOrIndex::Index(i.into_url()?)), (Some(r), None) => { - validate_package_name(r, "registry name", "")?; + restricted_names::validate_package_name(r, "registry name", "")?; Some(RegistryOrIndex::Registry(r.to_string())) } (Some(_), Some(_)) => { @@ -795,7 +849,7 @@ Run `{cmd}` to see possible targets." match self._value_of("registry").map(|s| s.to_string()) { None => config.default_registry(), Some(registry) => { - validate_package_name(®istry, "registry name", "")?; + restricted_names::validate_package_name(®istry, "registry name", "")?; Ok(Some(registry)) } } diff --git a/src/tools/cargo/src/cargo/util/config/mod.rs b/src/tools/cargo/src/cargo/util/config/mod.rs index b87f98afd..50153466b 100644 --- a/src/tools/cargo/src/cargo/util/config/mod.rs +++ b/src/tools/cargo/src/cargo/util/config/mod.rs @@ -49,6 +49,7 @@ //! translate from `ConfigValue` and environment variables to the caller's //! desired type. +use crate::util::cache_lock::{CacheLock, CacheLockMode, CacheLocker}; use std::borrow::Cow; use std::cell::{RefCell, RefMut}; use std::collections::hash_map::Entry::{Occupied, Vacant}; @@ -58,7 +59,7 @@ use std::ffi::{OsStr, OsString}; use std::fmt; use std::fs::{self, File}; use std::io::prelude::*; -use std::io::{self, SeekFrom}; +use std::io::SeekFrom; use std::mem; use std::path::{Path, PathBuf}; use std::str::FromStr; @@ -75,10 +76,9 @@ use crate::sources::CRATES_IO_REGISTRY; use crate::util::errors::CargoResult; use crate::util::network::http::configure_http_handle; use crate::util::network::http::http_handle; -use crate::util::toml as cargo_toml; use crate::util::{internal, CanonicalUrl}; use crate::util::{try_canonicalize, validate_package_name}; -use crate::util::{FileLock, Filesystem, IntoUrl, IntoUrlWithBase, Rustc}; +use crate::util::{Filesystem, IntoUrl, IntoUrlWithBase, Rustc}; use anyhow::{anyhow, bail, format_err, Context as _}; use cargo_credential::Secret; use cargo_util::paths; @@ -215,9 +215,8 @@ pub struct Config { credential_cache: LazyCell<RefCell<HashMap<CanonicalUrl, CredentialCacheValue>>>, /// Cache of registry config from from the `[registries]` table. registry_config: LazyCell<RefCell<HashMap<SourceId, Option<RegistryConfig>>>>, - /// Lock, if held, of the global package cache along with the number of - /// acquisitions so far. - package_cache_lock: RefCell<Option<(Option<FileLock>, usize)>>, + /// Locks on the package and index caches. + package_cache_lock: CacheLocker, /// Cached configuration parsed by Cargo http_config: LazyCell<CargoHttpConfig>, future_incompat_config: LazyCell<CargoFutureIncompatConfig>, @@ -307,7 +306,7 @@ impl Config { updated_sources: LazyCell::new(), credential_cache: LazyCell::new(), registry_config: LazyCell::new(), - package_cache_lock: RefCell::new(None), + package_cache_lock: CacheLocker::new(), http_config: LazyCell::new(), future_incompat_config: LazyCell::new(), net_config: LazyCell::new(), @@ -1032,6 +1031,9 @@ impl Config { self.shell().set_verbosity(verbosity); self.shell().set_color_choice(color)?; + if let Some(hyperlinks) = term.hyperlinks { + self.shell().set_hyperlinks(hyperlinks)?; + } self.progress_config = term.progress.unwrap_or_default(); self.extra_verbose = extra_verbose; self.frozen = frozen; @@ -1195,7 +1197,7 @@ impl Config { } let contents = fs::read_to_string(path) .with_context(|| format!("failed to read configuration file `{}`", path.display()))?; - let toml = cargo_toml::parse_document(&contents, path, self).with_context(|| { + let toml = parse_document(&contents, path, self).with_context(|| { format!("could not parse TOML configuration in `{}`", path.display()) })?; let def = match why_load { @@ -1876,10 +1878,20 @@ impl Config { T::deserialize(d).map_err(|e| e.into()) } - pub fn assert_package_cache_locked<'a>(&self, f: &'a Filesystem) -> &'a Path { + /// Obtain a [`Path`] from a [`Filesystem`], verifying that the + /// appropriate lock is already currently held. + /// + /// Locks are usually acquired via [`Config::acquire_package_cache_lock`] + /// or [`Config::try_acquire_package_cache_lock`]. + #[track_caller] + pub fn assert_package_cache_locked<'a>( + &self, + mode: CacheLockMode, + f: &'a Filesystem, + ) -> &'a Path { let ret = f.as_path_unlocked(); assert!( - self.package_cache_lock.borrow().is_some(), + self.package_cache_lock.is_locked(mode), "package cache lock is not currently held, Cargo forgot to call \ `acquire_package_cache_lock` before we got to this stack frame", ); @@ -1887,72 +1899,26 @@ impl Config { ret } - /// Acquires an exclusive lock on the global "package cache" + /// Acquires a lock on the global "package cache", blocking if another + /// cargo holds the lock. /// - /// This lock is global per-process and can be acquired recursively. An RAII - /// structure is returned to release the lock, and if this process - /// abnormally terminates the lock is also released. - pub fn acquire_package_cache_lock(&self) -> CargoResult<PackageCacheLock<'_>> { - let mut slot = self.package_cache_lock.borrow_mut(); - match *slot { - // We've already acquired the lock in this process, so simply bump - // the count and continue. - Some((_, ref mut cnt)) => { - *cnt += 1; - } - None => { - let path = ".package-cache"; - let desc = "package cache"; - - // First, attempt to open an exclusive lock which is in general - // the purpose of this lock! - // - // If that fails because of a readonly filesystem or a - // permission error, though, then we don't really want to fail - // just because of this. All files that this lock protects are - // in subfolders, so they're assumed by Cargo to also be - // readonly or have invalid permissions for us to write to. If - // that's the case, then we don't really need to grab a lock in - // the first place here. - // - // Despite this we attempt to grab a readonly lock. This means - // that if our read-only folder is shared read-write with - // someone else on the system we should synchronize with them, - // but if we can't even do that then we did our best and we just - // keep on chugging elsewhere. - match self.home_path.open_rw(path, self, desc) { - Ok(lock) => *slot = Some((Some(lock), 1)), - Err(e) => { - if maybe_readonly(&e) { - let lock = self.home_path.open_ro(path, self, desc).ok(); - *slot = Some((lock, 1)); - return Ok(PackageCacheLock(self)); - } - - Err(e).with_context(|| "failed to acquire package cache lock")?; - } - } - } - } - return Ok(PackageCacheLock(self)); - - fn maybe_readonly(err: &anyhow::Error) -> bool { - err.chain().any(|err| { - if let Some(io) = err.downcast_ref::<io::Error>() { - if io.kind() == io::ErrorKind::PermissionDenied { - return true; - } - - #[cfg(unix)] - return io.raw_os_error() == Some(libc::EROFS); - } - - false - }) - } + /// See [`crate::util::cache_lock`] for an in-depth discussion of locking + /// and lock modes. + pub fn acquire_package_cache_lock(&self, mode: CacheLockMode) -> CargoResult<CacheLock<'_>> { + self.package_cache_lock.lock(self, mode) } - pub fn release_package_cache_lock(&self) {} + /// Acquires a lock on the global "package cache", returning `None` if + /// another cargo holds the lock. + /// + /// See [`crate::util::cache_lock`] for an in-depth discussion of locking + /// and lock modes. + pub fn try_acquire_package_cache_lock( + &self, + mode: CacheLockMode, + ) -> CargoResult<Option<CacheLock<'_>>> { + self.package_cache_lock.try_lock(self, mode) + } } /// Internal error for serde errors. @@ -2271,7 +2237,7 @@ pub fn save_credentials( let mut file = { cfg.home_path.create_dir()?; cfg.home_path - .open_rw(filename, cfg, "credentials' config file")? + .open_rw_exclusive_create(filename, cfg, "credentials' config file")? }; let mut contents = String::new(); @@ -2282,7 +2248,7 @@ pub fn save_credentials( ) })?; - let mut toml = cargo_toml::parse_document(&contents, file.path(), cfg)?; + let mut toml = parse_document(&contents, file.path(), cfg)?; // Move the old token location to the new one. if let Some(token) = toml.remove("token") { @@ -2390,19 +2356,6 @@ pub fn save_credentials( } } -pub struct PackageCacheLock<'a>(&'a Config); - -impl Drop for PackageCacheLock<'_> { - fn drop(&mut self) { - let mut slot = self.0.package_cache_lock.borrow_mut(); - let (_, cnt) = slot.as_mut().unwrap(); - *cnt -= 1; - if *cnt == 0 { - *slot = None; - } - } -} - #[derive(Debug, Default, Deserialize, PartialEq)] #[serde(rename_all = "kebab-case")] pub struct CargoHttpConfig { @@ -2609,6 +2562,7 @@ struct TermConfig { verbose: Option<bool>, quiet: Option<bool>, color: Option<String>, + hyperlinks: Option<bool>, #[serde(default)] #[serde(deserialize_with = "progress_or_string")] progress: Option<ProgressConfig>, @@ -2761,6 +2715,11 @@ impl EnvConfigValue { pub type EnvConfig = HashMap<String, EnvConfigValue>; +fn parse_document(toml: &str, _file: &Path, _config: &Config) -> CargoResult<toml::Table> { + // At the moment, no compatibility checks are needed. + toml.parse().map_err(Into::into) +} + /// A type to deserialize a list of strings from a toml file. /// /// Supports deserializing either a whitespace-separated list of arguments in a diff --git a/src/tools/cargo/src/cargo/util/config/target.rs b/src/tools/cargo/src/cargo/util/config/target.rs index 6d6a6beff..0e2029ffc 100644 --- a/src/tools/cargo/src/cargo/util/config/target.rs +++ b/src/tools/cargo/src/cargo/util/config/target.rs @@ -137,10 +137,6 @@ fn parse_links_overrides( config: &Config, ) -> CargoResult<BTreeMap<String, BuildOutput>> { let mut links_overrides = BTreeMap::new(); - let extra_check_cfg = match config.cli_unstable().check_cfg { - Some((_, _, _, output)) => output, - None => false, - }; for (lib_name, value) in links { // Skip these keys, it shares the namespace with `TargetConfig`. @@ -207,12 +203,12 @@ fn parse_links_overrides( output.cfgs.extend(list.iter().map(|v| v.0.clone())); } "rustc-check-cfg" => { - if extra_check_cfg { + if config.cli_unstable().check_cfg { let list = value.list(key)?; output.check_cfgs.extend(list.iter().map(|v| v.0.clone())); } else { config.shell().warn(format!( - "target config `{}.{}` requires -Zcheck-cfg=output flag", + "target config `{}.{}` requires -Zcheck-cfg flag", target_key, key ))?; } diff --git a/src/tools/cargo/src/cargo/util/flock.rs b/src/tools/cargo/src/cargo/util/flock.rs index aa056c965..3fb2397f7 100644 --- a/src/tools/cargo/src/cargo/util/flock.rs +++ b/src/tools/cargo/src/cargo/util/flock.rs @@ -1,3 +1,12 @@ +//! File-locking support. +//! +//! This module defines the [`Filesystem`] type which is an abstraction over a +//! filesystem, ensuring that access to the filesystem is only done through +//! coordinated locks. +//! +//! The [`FileLock`] type represents a locked file, and provides access to the +//! file. + use std::fs::{File, OpenOptions}; use std::io; use std::io::{Read, Seek, SeekFrom, Write}; @@ -10,18 +19,22 @@ use anyhow::Context as _; use cargo_util::paths; use sys::*; +/// A locked file. +/// +/// This provides access to file while holding a lock on the file. This type +/// implements the [`Read`], [`Write`], and [`Seek`] traits to provide access +/// to the underlying file. +/// +/// Locks are either shared (multiple processes can access the file) or +/// exclusive (only one process can access the file). +/// +/// This type is created via methods on the [`Filesystem`] type. +/// +/// When this value is dropped, the lock will be released. #[derive(Debug)] pub struct FileLock { f: Option<File>, path: PathBuf, - state: State, -} - -#[derive(PartialEq, Debug)] -enum State { - Unlocked, - Shared, - Exclusive, } impl FileLock { @@ -35,13 +48,11 @@ impl FileLock { /// Note that special care must be taken to ensure that the path is not /// referenced outside the lifetime of this lock. pub fn path(&self) -> &Path { - assert_ne!(self.state, State::Unlocked); &self.path } /// Returns the parent path containing this file pub fn parent(&self) -> &Path { - assert_ne!(self.state, State::Unlocked); self.path.parent().unwrap() } @@ -91,9 +102,9 @@ impl Write for FileLock { impl Drop for FileLock { fn drop(&mut self) { - if self.state != State::Unlocked { - if let Some(f) = self.f.take() { - let _ = unlock(&f); + if let Some(f) = self.f.take() { + if let Err(e) = unlock(&f) { + tracing::warn!("failed to release lock: {e:?}"); } } } @@ -105,6 +116,32 @@ impl Drop for FileLock { /// The `Path` of a filesystem cannot be learned unless it's done in a locked /// fashion, and otherwise functions on this structure are prepared to handle /// concurrent invocations across multiple instances of Cargo. +/// +/// The methods on `Filesystem` that open files return a [`FileLock`] which +/// holds the lock, and that type provides methods for accessing the +/// underlying file. +/// +/// If the blocking methods (like [`Filesystem::open_ro_shared`]) detect that +/// they will block, then they will display a message to the user letting them +/// know it is blocked. There are non-blocking variants starting with the +/// `try_` prefix like [`Filesystem::try_open_ro_shared_create`]. +/// +/// The behavior of locks acquired by the `Filesystem` depend on the operating +/// system. On unix-like system, they are advisory using [`flock`], and thus +/// not enforced against processes which do not try to acquire the lock. On +/// Windows, they are mandatory using [`LockFileEx`], enforced against all +/// processes. +/// +/// This **does not** guarantee that a lock is acquired. In some cases, for +/// example on filesystems that don't support locking, it will return a +/// [`FileLock`] even though the filesystem lock was not acquired. This is +/// intended to provide a graceful fallback instead of refusing to work. +/// Usually there aren't multiple processes accessing the same resource. In +/// that case, it is the user's responsibility to not run concurrent +/// processes. +/// +/// [`flock`]: https://linux.die.net/man/2/flock +/// [`LockFileEx`]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-lockfileex #[derive(Clone, Debug)] pub struct Filesystem { root: PathBuf, @@ -157,68 +194,119 @@ impl Filesystem { self.root.display() } - /// Opens exclusive access to a file, returning the locked version of a - /// file. + /// Opens read-write exclusive access to a file, returning the locked + /// version of a file. /// /// This function will create a file at `path` if it doesn't already exist /// (including intermediate directories), and then it will acquire an /// exclusive lock on `path`. If the process must block waiting for the - /// lock, the `msg` is printed to `config`. + /// lock, the `msg` is printed to [`Config`]. /// /// The returned file can be accessed to look at the path and also has /// read/write access to the underlying file. - pub fn open_rw<P>(&self, path: P, config: &Config, msg: &str) -> CargoResult<FileLock> + pub fn open_rw_exclusive_create<P>( + &self, + path: P, + config: &Config, + msg: &str, + ) -> CargoResult<FileLock> where P: AsRef<Path>, { - self.open( - path.as_ref(), - OpenOptions::new().read(true).write(true).create(true), - State::Exclusive, - config, - msg, - ) + let mut opts = OpenOptions::new(); + opts.read(true).write(true).create(true); + let (path, f) = self.open(path.as_ref(), &opts, true)?; + acquire(config, msg, &path, &|| try_lock_exclusive(&f), &|| { + lock_exclusive(&f) + })?; + Ok(FileLock { f: Some(f), path }) } - /// Opens shared access to a file, returning the locked version of a file. + /// A non-blocking version of [`Filesystem::open_rw_exclusive_create`]. + /// + /// Returns `None` if the operation would block due to another process + /// holding the lock. + pub fn try_open_rw_exclusive_create<P: AsRef<Path>>( + &self, + path: P, + ) -> CargoResult<Option<FileLock>> { + let mut opts = OpenOptions::new(); + opts.read(true).write(true).create(true); + let (path, f) = self.open(path.as_ref(), &opts, true)?; + if try_acquire(&path, &|| try_lock_exclusive(&f))? { + Ok(Some(FileLock { f: Some(f), path })) + } else { + Ok(None) + } + } + + /// Opens read-only shared access to a file, returning the locked version of a file. /// /// This function will fail if `path` doesn't already exist, but if it does /// then it will acquire a shared lock on `path`. If the process must block - /// waiting for the lock, the `msg` is printed to `config`. + /// waiting for the lock, the `msg` is printed to [`Config`]. /// /// The returned file can be accessed to look at the path and also has read /// access to the underlying file. Any writes to the file will return an /// error. - pub fn open_ro<P>(&self, path: P, config: &Config, msg: &str) -> CargoResult<FileLock> + pub fn open_ro_shared<P>(&self, path: P, config: &Config, msg: &str) -> CargoResult<FileLock> where P: AsRef<Path>, { - self.open( - path.as_ref(), - OpenOptions::new().read(true), - State::Shared, - config, - msg, - ) + let (path, f) = self.open(path.as_ref(), &OpenOptions::new().read(true), false)?; + acquire(config, msg, &path, &|| try_lock_shared(&f), &|| { + lock_shared(&f) + })?; + Ok(FileLock { f: Some(f), path }) } - fn open( + /// Opens read-only shared access to a file, returning the locked version of a file. + /// + /// Compared to [`Filesystem::open_ro_shared`], this will create the file + /// (and any directories in the parent) if the file does not already + /// exist. + pub fn open_ro_shared_create<P: AsRef<Path>>( &self, - path: &Path, - opts: &OpenOptions, - state: State, + path: P, config: &Config, msg: &str, ) -> CargoResult<FileLock> { - let path = self.root.join(path); + let mut opts = OpenOptions::new(); + opts.read(true).write(true).create(true); + let (path, f) = self.open(path.as_ref(), &opts, true)?; + acquire(config, msg, &path, &|| try_lock_shared(&f), &|| { + lock_shared(&f) + })?; + Ok(FileLock { f: Some(f), path }) + } - // If we want an exclusive lock then if we fail because of NotFound it's - // likely because an intermediate directory didn't exist, so try to - // create the directory and then continue. + /// A non-blocking version of [`Filesystem::open_ro_shared_create`]. + /// + /// Returns `None` if the operation would block due to another process + /// holding the lock. + pub fn try_open_ro_shared_create<P: AsRef<Path>>( + &self, + path: P, + ) -> CargoResult<Option<FileLock>> { + let mut opts = OpenOptions::new(); + opts.read(true).write(true).create(true); + let (path, f) = self.open(path.as_ref(), &opts, true)?; + if try_acquire(&path, &|| try_lock_shared(&f))? { + Ok(Some(FileLock { f: Some(f), path })) + } else { + Ok(None) + } + } + + fn open(&self, path: &Path, opts: &OpenOptions, create: bool) -> CargoResult<(PathBuf, File)> { + let path = self.root.join(path); let f = opts .open(&path) .or_else(|e| { - if e.kind() == io::ErrorKind::NotFound && state == State::Exclusive { + // If we were requested to create this file, and there was a + // NotFound error, then that was likely due to missing + // intermediate directories. Try creating them and try again. + if e.kind() == io::ErrorKind::NotFound && create { paths::create_dir_all(path.parent().unwrap())?; Ok(opts.open(&path)?) } else { @@ -226,24 +314,7 @@ impl Filesystem { } }) .with_context(|| format!("failed to open: {}", path.display()))?; - match state { - State::Exclusive => { - acquire(config, msg, &path, &|| try_lock_exclusive(&f), &|| { - lock_exclusive(&f) - })?; - } - State::Shared => { - acquire(config, msg, &path, &|| try_lock_shared(&f), &|| { - lock_shared(&f) - })?; - } - State::Unlocked => {} - } - Ok(FileLock { - f: Some(f), - path, - state, - }) + Ok((path, f)) } } @@ -259,28 +330,7 @@ impl PartialEq<Filesystem> for Path { } } -/// Acquires a lock on a file in a "nice" manner. -/// -/// Almost all long-running blocking actions in Cargo have a status message -/// associated with them as we're not sure how long they'll take. Whenever a -/// conflicted file lock happens, this is the case (we're not sure when the lock -/// will be released). -/// -/// This function will acquire the lock on a `path`, printing out a nice message -/// to the console if we have to wait for it. It will first attempt to use `try` -/// to acquire a lock on the crate, and in the case of contention it will emit a -/// status message based on `msg` to `config`'s shell, and then use `block` to -/// block waiting to acquire a lock. -/// -/// Returns an error if the lock could not be acquired or if any error other -/// than a contention error happens. -fn acquire( - config: &Config, - msg: &str, - path: &Path, - lock_try: &dyn Fn() -> io::Result<()>, - lock_block: &dyn Fn() -> io::Result<()>, -) -> CargoResult<()> { +fn try_acquire(path: &Path, lock_try: &dyn Fn() -> io::Result<()>) -> CargoResult<bool> { // File locking on Unix is currently implemented via `flock`, which is known // to be broken on NFS. We could in theory just ignore errors that happen on // NFS, but apparently the failure mode [1] for `flock` on NFS is **blocking @@ -292,16 +342,17 @@ fn acquire( // // [1]: https://github.com/rust-lang/cargo/issues/2615 if is_on_nfs_mount(path) { - return Ok(()); + tracing::debug!("{path:?} appears to be an NFS mount, not trying to lock"); + return Ok(true); } match lock_try() { - Ok(()) => return Ok(()), + Ok(()) => return Ok(true), // In addition to ignoring NFS which is commonly not working we also // just ignore locking on filesystems that look like they don't // implement file locking. - Err(e) if error_unsupported(&e) => return Ok(()), + Err(e) if error_unsupported(&e) => return Ok(true), Err(e) => { if !error_contended(&e) { @@ -311,36 +362,64 @@ fn acquire( } } } + Ok(false) +} + +/// Acquires a lock on a file in a "nice" manner. +/// +/// Almost all long-running blocking actions in Cargo have a status message +/// associated with them as we're not sure how long they'll take. Whenever a +/// conflicted file lock happens, this is the case (we're not sure when the lock +/// will be released). +/// +/// This function will acquire the lock on a `path`, printing out a nice message +/// to the console if we have to wait for it. It will first attempt to use `try` +/// to acquire a lock on the crate, and in the case of contention it will emit a +/// status message based on `msg` to [`Config`]'s shell, and then use `block` to +/// block waiting to acquire a lock. +/// +/// Returns an error if the lock could not be acquired or if any error other +/// than a contention error happens. +fn acquire( + config: &Config, + msg: &str, + path: &Path, + lock_try: &dyn Fn() -> io::Result<()>, + lock_block: &dyn Fn() -> io::Result<()>, +) -> CargoResult<()> { + if try_acquire(path, lock_try)? { + return Ok(()); + } let msg = format!("waiting for file lock on {}", msg); config .shell() .status_with_color("Blocking", &msg, &style::NOTE)?; lock_block().with_context(|| format!("failed to lock file: {}", path.display()))?; - return Ok(()); + Ok(()) +} - #[cfg(all(target_os = "linux", not(target_env = "musl")))] - fn is_on_nfs_mount(path: &Path) -> bool { - use std::ffi::CString; - use std::mem; - use std::os::unix::prelude::*; +#[cfg(all(target_os = "linux", not(target_env = "musl")))] +fn is_on_nfs_mount(path: &Path) -> bool { + use std::ffi::CString; + use std::mem; + use std::os::unix::prelude::*; - let Ok(path) = CString::new(path.as_os_str().as_bytes()) else { - return false; - }; + let Ok(path) = CString::new(path.as_os_str().as_bytes()) else { + return false; + }; - unsafe { - let mut buf: libc::statfs = mem::zeroed(); - let r = libc::statfs(path.as_ptr(), &mut buf); + unsafe { + let mut buf: libc::statfs = mem::zeroed(); + let r = libc::statfs(path.as_ptr(), &mut buf); - r == 0 && buf.f_type as u32 == libc::NFS_SUPER_MAGIC as u32 - } + r == 0 && buf.f_type as u32 == libc::NFS_SUPER_MAGIC as u32 } +} - #[cfg(any(not(target_os = "linux"), target_env = "musl"))] - fn is_on_nfs_mount(_path: &Path) -> bool { - false - } +#[cfg(any(not(target_os = "linux"), target_env = "musl"))] +fn is_on_nfs_mount(_path: &Path) -> bool { + false } #[cfg(unix)] diff --git a/src/tools/cargo/src/cargo/util/hostname.rs b/src/tools/cargo/src/cargo/util/hostname.rs new file mode 100644 index 000000000..3f53c9cf6 --- /dev/null +++ b/src/tools/cargo/src/cargo/util/hostname.rs @@ -0,0 +1,77 @@ +// Copied from https://github.com/BurntSushi/ripgrep/blob/7099e174acbcbd940f57e4ab4913fee4040c826e/crates/cli/src/hostname.rs + +use std::{ffi::OsString, io}; + +/// Returns the hostname of the current system. +/// +/// It is unusual, although technically possible, for this routine to return +/// an error. It is difficult to list out the error conditions, but one such +/// possibility is platform support. +/// +/// # Platform specific behavior +/// +/// On Unix, this returns the result of the `gethostname` function from the +/// `libc` linked into the program. +pub fn hostname() -> io::Result<OsString> { + #[cfg(unix)] + { + gethostname() + } + #[cfg(not(unix))] + { + Err(io::Error::new( + io::ErrorKind::Other, + "hostname could not be found on unsupported platform", + )) + } +} + +#[cfg(unix)] +fn gethostname() -> io::Result<OsString> { + use std::os::unix::ffi::OsStringExt; + + // SAFETY: There don't appear to be any safety requirements for calling + // sysconf. + let limit = unsafe { libc::sysconf(libc::_SC_HOST_NAME_MAX) }; + if limit == -1 { + // It is in theory possible for sysconf to return -1 for a limit but + // *not* set errno, in which case, io::Error::last_os_error is + // indeterminate. But untangling that is super annoying because std + // doesn't expose any unix-specific APIs for inspecting the errno. (We + // could do it ourselves, but it just doesn't seem worth doing?) + return Err(io::Error::last_os_error()); + } + let Ok(maxlen) = usize::try_from(limit) else { + let msg = format!("host name max limit ({}) overflowed usize", limit); + return Err(io::Error::new(io::ErrorKind::Other, msg)); + }; + // maxlen here includes the NUL terminator. + let mut buf = vec![0; maxlen]; + // SAFETY: The pointer we give is valid as it is derived directly from a + // Vec. Similarly, `maxlen` is the length of our Vec, and is thus valid + // to write to. + let rc = unsafe { libc::gethostname(buf.as_mut_ptr().cast::<libc::c_char>(), maxlen) }; + if rc == -1 { + return Err(io::Error::last_os_error()); + } + // POSIX says that if the hostname is bigger than `maxlen`, then it may + // write a truncate name back that is not necessarily NUL terminated (wtf, + // lol). So if we can't find a NUL terminator, then just give up. + let Some(zeropos) = buf.iter().position(|&b| b == 0) else { + let msg = "could not find NUL terminator in hostname"; + return Err(io::Error::new(io::ErrorKind::Other, msg)); + }; + buf.truncate(zeropos); + buf.shrink_to_fit(); + Ok(OsString::from_vec(buf)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn print_hostname() { + println!("{:?}", hostname()); + } +} diff --git a/src/tools/cargo/src/cargo/util/mod.rs b/src/tools/cargo/src/cargo/util/mod.rs index b4d14f038..fb4c4b39c 100644 --- a/src/tools/cargo/src/cargo/util/mod.rs +++ b/src/tools/cargo/src/cargo/util/mod.rs @@ -14,6 +14,7 @@ pub use self::flock::{FileLock, Filesystem}; pub use self::graph::Graph; pub use self::hasher::StableHasher; pub use self::hex::{hash_u64, short_hash, to_hex}; +pub use self::hostname::hostname; pub use self::into_url::IntoUrl; pub use self::into_url_with_base::IntoUrlWithBase; pub(crate) use self::io::LimitErrorReader; @@ -22,8 +23,7 @@ pub use self::progress::{Progress, ProgressStyle}; pub use self::queue::Queue; pub use self::restricted_names::validate_package_name; pub use self::rustc::Rustc; -pub use self::semver_ext::{OptVersionReq, PartialVersion, RustVersion, VersionExt, VersionReqExt}; -pub use self::to_semver::ToSemver; +pub use self::semver_ext::{OptVersionReq, RustVersion}; pub use self::vcs::{existing_vcs_repo, FossilRepo, GitRepo, HgRepo, PijulRepo}; pub use self::workspace::{ add_path_args, path_args, print_available_benches, print_available_binaries, @@ -31,6 +31,7 @@ pub use self::workspace::{ }; pub mod auth; +pub mod cache_lock; mod canonical_url; pub mod command_prelude; pub mod config; @@ -45,6 +46,7 @@ mod flock; pub mod graph; mod hasher; pub mod hex; +mod hostname; pub mod important_paths; pub mod interning; pub mod into_url; @@ -61,7 +63,6 @@ pub mod restricted_names; pub mod rustc; mod semver_ext; pub mod style; -pub mod to_semver; pub mod toml; pub mod toml_mut; mod vcs; diff --git a/src/tools/cargo/src/cargo/util/restricted_names.rs b/src/tools/cargo/src/cargo/util/restricted_names.rs index 2c3eaa9e1..f61249775 100644 --- a/src/tools/cargo/src/cargo/util/restricted_names.rs +++ b/src/tools/cargo/src/cargo/util/restricted_names.rs @@ -120,3 +120,82 @@ pub fn is_windows_reserved_path(path: &Path) -> bool { pub fn is_glob_pattern<T: AsRef<str>>(name: T) -> bool { name.as_ref().contains(&['*', '?', '[', ']'][..]) } + +/// Validate dir-names and profile names according to RFC 2678. +pub fn validate_profile_name(name: &str) -> CargoResult<()> { + if let Some(ch) = name + .chars() + .find(|ch| !ch.is_alphanumeric() && *ch != '_' && *ch != '-') + { + bail!( + "invalid character `{}` in profile name `{}`\n\ + Allowed characters are letters, numbers, underscore, and hyphen.", + ch, + name + ); + } + + const SEE_DOCS: &str = "See https://doc.rust-lang.org/cargo/reference/profiles.html \ + for more on configuring profiles."; + + let lower_name = name.to_lowercase(); + if lower_name == "debug" { + bail!( + "profile name `{}` is reserved\n\ + To configure the default development profile, use the name `dev` \ + as in [profile.dev]\n\ + {}", + name, + SEE_DOCS + ); + } + if lower_name == "build-override" { + bail!( + "profile name `{}` is reserved\n\ + To configure build dependency settings, use [profile.dev.build-override] \ + and [profile.release.build-override]\n\ + {}", + name, + SEE_DOCS + ); + } + + // These are some arbitrary reservations. We have no plans to use + // these, but it seems safer to reserve a few just in case we want to + // add more built-in profiles in the future. We can also uses special + // syntax like cargo:foo if needed. But it is unlikely these will ever + // be used. + if matches!( + lower_name.as_str(), + "build" + | "check" + | "clean" + | "config" + | "fetch" + | "fix" + | "install" + | "metadata" + | "package" + | "publish" + | "report" + | "root" + | "run" + | "rust" + | "rustc" + | "rustdoc" + | "target" + | "tmp" + | "uninstall" + ) || lower_name.starts_with("cargo") + { + bail!( + "profile name `{}` is reserved\n\ + Please choose a different name.\n\ + {}", + name, + SEE_DOCS + ); + } + + Ok(()) +} diff --git a/src/tools/cargo/src/cargo/util/rustc.rs b/src/tools/cargo/src/cargo/util/rustc.rs index d1bb3981d..f51580f29 100644 --- a/src/tools/cargo/src/cargo/util/rustc.rs +++ b/src/tools/cargo/src/cargo/util/rustc.rs @@ -28,6 +28,8 @@ pub struct Rustc { pub version: semver::Version, /// The host triple (arch-platform-OS), this comes from verbose_version. pub host: InternedString, + /// The rustc full commit hash, this comes from `verbose_version`. + pub commit_hash: Option<String>, cache: Mutex<Cache>, } @@ -80,6 +82,17 @@ impl Rustc { verbose_version ) })?; + let commit_hash = extract("commit-hash: ").ok().map(|hash| { + debug_assert!( + hash.chars().all(|ch| ch.is_ascii_hexdigit()), + "commit hash must be a hex string" + ); + debug_assert!( + hash.len() == 40 || hash.len() == 64, + "hex string must be generated from sha1 or sha256" + ); + hash.to_string() + }); Ok(Rustc { path, @@ -88,6 +101,7 @@ impl Rustc { verbose_version, version, host, + commit_hash, cache: Mutex::new(cache), }) } diff --git a/src/tools/cargo/src/cargo/util/semver_ext.rs b/src/tools/cargo/src/cargo/util/semver_ext.rs index 5839d85d2..561cf140e 100644 --- a/src/tools/cargo/src/cargo/util/semver_ext.rs +++ b/src/tools/cargo/src/cargo/util/semver_ext.rs @@ -1,52 +1,37 @@ -use semver::{Comparator, Op, Version, VersionReq}; -use serde_untagged::UntaggedEnumVisitor; use std::fmt::{self, Display}; +use semver::{Op, Version, VersionReq}; +use serde_untagged::UntaggedEnumVisitor; + +use crate::util_semver::PartialVersion; +use crate::util_semver::VersionExt as _; + #[derive(PartialEq, Eq, Hash, Clone, Debug)] pub enum OptVersionReq { Any, Req(VersionReq), /// The exact locked version and the original version requirement. Locked(Version, VersionReq), -} - -pub trait VersionExt { - fn is_prerelease(&self) -> bool; -} - -pub trait VersionReqExt { - fn exact(version: &Version) -> Self; -} - -impl VersionExt for Version { - fn is_prerelease(&self) -> bool { - !self.pre.is_empty() - } -} - -impl VersionReqExt for VersionReq { - fn exact(version: &Version) -> Self { - VersionReq { - comparators: vec![Comparator { - op: Op::Exact, - major: version.major, - minor: Some(version.minor), - patch: Some(version.patch), - pre: version.pre.clone(), - }], - } - } + /// The exact requested version and the original version requirement. + UpdatePrecise(Version, VersionReq), } impl OptVersionReq { pub fn exact(version: &Version) -> Self { - OptVersionReq::Req(VersionReq::exact(version)) + OptVersionReq::Req(version.to_exact_req()) + } + + // Since some registries have allowed crate versions to differ only by build metadata, + // A query using OptVersionReq::exact return nondeterministic results. + // So we `lock_to` the exact version were interested in. + pub fn lock_to_exact(version: &Version) -> Self { + OptVersionReq::Locked(version.clone(), version.to_exact_req()) } pub fn is_exact(&self) -> bool { match self { OptVersionReq::Any => false, - OptVersionReq::Req(req) => { + OptVersionReq::Req(req) | OptVersionReq::UpdatePrecise(_, req) => { req.comparators.len() == 1 && { let cmp = &req.comparators[0]; cmp.op == Op::Exact && cmp.minor.is_some() && cmp.patch.is_some() @@ -62,8 +47,18 @@ impl OptVersionReq { let version = version.clone(); *self = match self { Any => Locked(version, VersionReq::STAR), - Req(req) => Locked(version, req.clone()), - Locked(_, req) => Locked(version, req.clone()), + Req(req) | Locked(_, req) | UpdatePrecise(_, req) => Locked(version, req.clone()), + }; + } + + pub fn update_precise(&mut self, version: &Version) { + use OptVersionReq::*; + let version = version.clone(); + *self = match self { + Any => UpdatePrecise(version, VersionReq::STAR), + Req(req) | Locked(_, req) | UpdatePrecise(_, req) => { + UpdatePrecise(version, req.clone()) + } }; } @@ -84,10 +79,31 @@ impl OptVersionReq { OptVersionReq::Any => true, OptVersionReq::Req(req) => req.matches(version), OptVersionReq::Locked(v, _) => { + // Generally, cargo is of the opinion that semver metadata should be ignored. + // If your registry has two versions that only differing metadata you get the bugs you deserve. + // We also believe that lock files should ensure reproducibility + // and protect against mutations from the registry. + // In this circumstance these two goals are in conflict, and we pick reproducibility. + // If the lock file tells us that there is a version called `1.0.0+bar` then + // we should not silently use `1.0.0+foo` even though they have the same version. + v == version + } + OptVersionReq::UpdatePrecise(v, _) => { + // This is used for the `--precise` field of cargo update. + // + // Unfortunately crates.io allowed versions to differ only + // by build metadata. This shouldn't be allowed, but since + // it is, this will honor it if requested. + // + // In that context we treat a requirement that does not have + // build metadata as allowing any metadata. But, if a requirement + // has build metadata, then we only allow it to match the exact + // metadata. v.major == version.major && v.minor == version.minor && v.patch == version.patch && v.pre == version.pre + && (v.build == version.build || v.build.is_empty()) } } } @@ -97,8 +113,9 @@ impl Display for OptVersionReq { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { OptVersionReq::Any => f.write_str("*"), - OptVersionReq::Req(req) => Display::fmt(req, f), - OptVersionReq::Locked(_, req) => Display::fmt(req, f), + OptVersionReq::Req(req) + | OptVersionReq::Locked(_, req) + | OptVersionReq::UpdatePrecise(_, req) => Display::fmt(req, f), } } } @@ -153,207 +170,3 @@ impl Display for RustVersion { self.0.fmt(f) } } - -#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Debug)] -pub struct PartialVersion { - pub major: u64, - pub minor: Option<u64>, - pub patch: Option<u64>, - pub pre: Option<semver::Prerelease>, - pub build: Option<semver::BuildMetadata>, -} - -impl PartialVersion { - pub fn version(&self) -> Option<Version> { - Some(Version { - major: self.major, - minor: self.minor?, - patch: self.patch?, - pre: self.pre.clone().unwrap_or_default(), - build: self.build.clone().unwrap_or_default(), - }) - } - - pub fn caret_req(&self) -> VersionReq { - VersionReq { - comparators: vec![Comparator { - op: semver::Op::Caret, - major: self.major, - minor: self.minor, - patch: self.patch, - pre: self.pre.as_ref().cloned().unwrap_or_default(), - }], - } - } - - /// Check if this matches a version, including build metadata - /// - /// Build metadata does not affect version precedence but may be necessary for uniquely - /// identifying a package. - pub fn matches(&self, version: &Version) -> bool { - if !version.pre.is_empty() && self.pre.is_none() { - // Pre-release versions must be explicitly opted into, if for no other reason than to - // give us room to figure out and define the semantics - return false; - } - self.major == version.major - && self.minor.map(|f| f == version.minor).unwrap_or(true) - && self.patch.map(|f| f == version.patch).unwrap_or(true) - && self.pre.as_ref().map(|f| f == &version.pre).unwrap_or(true) - && self - .build - .as_ref() - .map(|f| f == &version.build) - .unwrap_or(true) - } -} - -impl From<semver::Version> for PartialVersion { - fn from(ver: semver::Version) -> Self { - let pre = if ver.pre.is_empty() { - None - } else { - Some(ver.pre) - }; - let build = if ver.build.is_empty() { - None - } else { - Some(ver.build) - }; - Self { - major: ver.major, - minor: Some(ver.minor), - patch: Some(ver.patch), - pre, - build, - } - } -} - -impl std::str::FromStr for PartialVersion { - type Err = anyhow::Error; - - fn from_str(value: &str) -> Result<Self, Self::Err> { - if is_req(value) { - anyhow::bail!("unexpected version requirement, expected a version like \"1.32\"") - } - match semver::Version::parse(value) { - Ok(ver) => Ok(ver.into()), - Err(_) => { - // HACK: Leverage `VersionReq` for partial version parsing - let mut version_req = match semver::VersionReq::parse(value) { - Ok(req) => req, - Err(_) if value.contains('-') => { - anyhow::bail!( - "unexpected prerelease field, expected a version like \"1.32\"" - ) - } - Err(_) if value.contains('+') => { - anyhow::bail!("unexpected build field, expected a version like \"1.32\"") - } - Err(_) => anyhow::bail!("expected a version like \"1.32\""), - }; - assert_eq!(version_req.comparators.len(), 1, "guaranteed by is_req"); - let comp = version_req.comparators.pop().unwrap(); - assert_eq!(comp.op, semver::Op::Caret, "guaranteed by is_req"); - let pre = if comp.pre.is_empty() { - None - } else { - Some(comp.pre) - }; - Ok(Self { - major: comp.major, - minor: comp.minor, - patch: comp.patch, - pre, - build: None, - }) - } - } - } -} - -impl Display for PartialVersion { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let major = self.major; - write!(f, "{major}")?; - if let Some(minor) = self.minor { - write!(f, ".{minor}")?; - } - if let Some(patch) = self.patch { - write!(f, ".{patch}")?; - } - if let Some(pre) = self.pre.as_ref() { - write!(f, "-{pre}")?; - } - if let Some(build) = self.build.as_ref() { - write!(f, "+{build}")?; - } - Ok(()) - } -} - -impl serde::Serialize for PartialVersion { - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where - S: serde::Serializer, - { - serializer.collect_str(self) - } -} - -impl<'de> serde::Deserialize<'de> for PartialVersion { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where - D: serde::Deserializer<'de>, - { - UntaggedEnumVisitor::new() - .expecting("SemVer version") - .string(|value| value.parse().map_err(serde::de::Error::custom)) - .deserialize(deserializer) - } -} - -fn is_req(value: &str) -> bool { - let Some(first) = value.chars().next() else { - return false; - }; - "<>=^~".contains(first) || value.contains('*') || value.contains(',') -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn locked_has_the_same_with_exact() { - fn test_versions(target_ver: &str, vers: &[&str]) { - let ver = Version::parse(target_ver).unwrap(); - let exact = OptVersionReq::exact(&ver); - let mut locked = exact.clone(); - locked.lock_to(&ver); - for v in vers { - let v = Version::parse(v).unwrap(); - assert_eq!(exact.matches(&v), locked.matches(&v)); - } - } - - test_versions( - "1.0.0", - &["1.0.0", "1.0.1", "0.9.9", "0.10.0", "0.1.0", "1.0.0-pre"], - ); - test_versions("0.9.0", &["0.9.0", "0.9.1", "1.9.0", "0.0.9", "0.9.0-pre"]); - test_versions("0.0.2", &["0.0.2", "0.0.1", "0.0.3", "0.0.2-pre"]); - test_versions( - "0.1.0-beta2.a", - &[ - "0.1.0-beta2.a", - "0.9.1", - "0.1.0", - "0.1.1-beta2.a", - "0.1.0-beta2", - ], - ); - test_versions("0.1.0+meta", &["0.1.0", "0.1.0+meta", "0.1.0+any"]); - } -} diff --git a/src/tools/cargo/src/cargo/util/to_semver.rs b/src/tools/cargo/src/cargo/util/to_semver.rs deleted file mode 100644 index 3cc9e5706..000000000 --- a/src/tools/cargo/src/cargo/util/to_semver.rs +++ /dev/null @@ -1,36 +0,0 @@ -use crate::util::errors::CargoResult; -use semver::Version; - -pub trait ToSemver { - fn to_semver(self) -> CargoResult<Version>; -} - -impl ToSemver for Version { - fn to_semver(self) -> CargoResult<Version> { - Ok(self) - } -} - -impl<'a> ToSemver for &'a str { - fn to_semver(self) -> CargoResult<Version> { - match Version::parse(self.trim()) { - Ok(v) => Ok(v), - Err(..) => Err(anyhow::format_err!( - "cannot parse '{}' as a SemVer version", - self - )), - } - } -} - -impl<'a> ToSemver for &'a String { - fn to_semver(self) -> CargoResult<Version> { - (**self).to_semver() - } -} - -impl<'a> ToSemver for &'a Version { - fn to_semver(self) -> CargoResult<Version> { - Ok(self.clone()) - } -} diff --git a/src/tools/cargo/src/cargo/util/toml/embedded.rs b/src/tools/cargo/src/cargo/util/toml/embedded.rs index 482268923..4c57195d4 100644 --- a/src/tools/cargo/src/cargo/util/toml/embedded.rs +++ b/src/tools/cargo/src/cargo/util/toml/embedded.rs @@ -6,11 +6,9 @@ use crate::Config; const DEFAULT_EDITION: crate::core::features::Edition = crate::core::features::Edition::LATEST_STABLE; -const DEFAULT_VERSION: &str = "0.0.0"; -const DEFAULT_PUBLISH: bool = false; const AUTO_FIELDS: &[&str] = &["autobins", "autoexamples", "autotests", "autobenches"]; -pub fn expand_manifest( +pub(super) fn expand_manifest( content: &str, path: &std::path::Path, config: &Config, @@ -123,9 +121,6 @@ fn expand_manifest_( package .entry("name".to_owned()) .or_insert(toml::Value::String(name)); - package - .entry("version".to_owned()) - .or_insert_with(|| toml::Value::String(DEFAULT_VERSION.to_owned())); package.entry("edition".to_owned()).or_insert_with(|| { let _ = config.shell().warn(format_args!( "`package.edition` is unspecified, defaulting to `{}`", @@ -136,9 +131,6 @@ fn expand_manifest_( package .entry("build".to_owned()) .or_insert_with(|| toml::Value::Boolean(false)); - package - .entry("publish".to_owned()) - .or_insert_with(|| toml::Value::Boolean(DEFAULT_PUBLISH)); for field in AUTO_FIELDS { package .entry(field.to_owned()) @@ -337,7 +329,7 @@ impl DocFragment { } #[derive(Clone, Copy, PartialEq, Debug)] -pub enum CommentKind { +enum CommentKind { Line, Block, } @@ -621,8 +613,6 @@ autotests = false build = false edition = "2021" name = "test-" -publish = false -version = "0.0.0" [profile.release] strip = true @@ -651,8 +641,6 @@ autotests = false build = false edition = "2021" name = "test-" -publish = false -version = "0.0.0" [profile.release] strip = true diff --git a/src/tools/cargo/src/cargo/util/toml/mod.rs b/src/tools/cargo/src/cargo/util/toml/mod.rs index 2e730b4e9..cb841476b 100644 --- a/src/tools/cargo/src/cargo/util/toml/mod.rs +++ b/src/tools/cargo/src/cargo/util/toml/mod.rs @@ -1,6 +1,5 @@ use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::ffi::OsStr; -use std::fmt::{self, Display, Write}; use std::path::{Path, PathBuf}; use std::rc::Rc; use std::str::{self, FromStr}; @@ -10,10 +9,6 @@ use cargo_platform::Platform; use cargo_util::paths; use itertools::Itertools; use lazycell::LazyCell; -use serde::de::{self, IntoDeserializer as _, Unexpected}; -use serde::ser; -use serde::{Deserialize, Serialize}; -use serde_untagged::UntaggedEnumVisitor; use tracing::{debug, trace}; use url::Url; @@ -28,12 +23,14 @@ use crate::core::{GitReference, PackageIdSpec, SourceId, WorkspaceConfig, Worksp use crate::sources::{CRATES_IO_INDEX, CRATES_IO_REGISTRY}; use crate::util::errors::{CargoResult, ManifestError}; use crate::util::interning::InternedString; +use crate::util::restricted_names; use crate::util::{ self, config::ConfigRelativePath, validate_package_name, Config, IntoUrl, OptVersionReq, RustVersion, }; -pub mod embedded; +mod embedded; +pub mod schema; mod targets; use self::targets::targets; @@ -100,7 +97,7 @@ fn read_manifest_from_str( let mut unused = BTreeSet::new(); let deserializer = toml::de::Deserializer::new(contents); - let manifest: TomlManifest = serde_ignored::deserialize(deserializer, |path| { + let manifest: schema::TomlManifest = serde_ignored::deserialize(deserializer, |path| { let mut key = String::new(); stringify(&mut key, &path); unused.insert(key); @@ -114,7 +111,6 @@ fn read_manifest_from_str( } }; - let manifest = Rc::new(manifest); if let Some(deps) = manifest .workspace .as_ref() @@ -130,8 +126,13 @@ fn read_manifest_from_str( } } return if manifest.project.is_some() || manifest.package.is_some() { - let (mut manifest, paths) = - TomlManifest::to_real_manifest(&manifest, embedded, source_id, package_root, config)?; + let (mut manifest, paths) = schema::TomlManifest::to_real_manifest( + manifest, + embedded, + source_id, + package_root, + config, + )?; add_unused(manifest.warnings_mut()); if manifest.targets().iter().all(|t| t.is_custom_build()) { bail!( @@ -143,7 +144,7 @@ fn read_manifest_from_str( Ok((EitherManifest::Real(manifest), paths)) } else { let (mut m, paths) = - TomlManifest::to_virtual_manifest(&manifest, source_id, package_root, config)?; + schema::TomlManifest::to_virtual_manifest(manifest, source_id, package_root, config)?; add_unused(m.warnings_mut()); Ok((EitherManifest::Virtual(m), paths)) }; @@ -174,11 +175,6 @@ fn read_manifest_from_str( } } -pub fn parse_document(toml: &str, _file: &Path, _config: &Config) -> CargoResult<toml::Table> { - // At the moment, no compatibility checks are needed. - toml.parse().map_err(Into::into) -} - /// Warn about paths that have been deprecated and may conflict. fn warn_on_deprecated(new_path: &str, name: &str, kind: &str, warnings: &mut Vec<String>) { let old_path = new_path.replace("-", "_"); @@ -188,1421 +184,7 @@ fn warn_on_deprecated(new_path: &str, name: &str, kind: &str, warnings: &mut Vec )) } -type TomlLibTarget = TomlTarget; -type TomlBinTarget = TomlTarget; -type TomlExampleTarget = TomlTarget; -type TomlTestTarget = TomlTarget; -type TomlBenchTarget = TomlTarget; - -#[derive(Clone, Debug, Serialize)] -#[serde(untagged)] -pub enum TomlDependency<P: Clone = String> { - /// In the simple format, only a version is specified, eg. - /// `package = "<version>"` - Simple(String), - /// The simple format is equivalent to a detailed dependency - /// specifying only a version, eg. - /// `package = { version = "<version>" }` - Detailed(DetailedTomlDependency<P>), -} - -impl<'de, P: Deserialize<'de> + Clone> de::Deserialize<'de> for TomlDependency<P> { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where - D: de::Deserializer<'de>, - { - UntaggedEnumVisitor::new() - .expecting( - "a version string like \"0.9.8\" or a \ - detailed dependency like { version = \"0.9.8\" }", - ) - .string(|value| Ok(TomlDependency::Simple(value.to_owned()))) - .map(|value| value.deserialize().map(TomlDependency::Detailed)) - .deserialize(deserializer) - } -} - -impl TomlDependency { - fn unused_keys(&self) -> Vec<String> { - match self { - TomlDependency::Simple(_) => vec![], - TomlDependency::Detailed(detailed) => detailed.other.keys().cloned().collect(), - } - } -} - -pub trait ResolveToPath { - fn resolve(&self, config: &Config) -> PathBuf; -} - -impl ResolveToPath for String { - fn resolve(&self, _: &Config) -> PathBuf { - self.into() - } -} - -impl ResolveToPath for ConfigRelativePath { - fn resolve(&self, c: &Config) -> PathBuf { - self.resolve_path(c) - } -} - -#[derive(Deserialize, Serialize, Clone, Debug)] -#[serde(rename_all = "kebab-case")] -pub struct DetailedTomlDependency<P: Clone = String> { - version: Option<String>, - registry: Option<String>, - /// The URL of the `registry` field. - /// This is an internal implementation detail. When Cargo creates a - /// package, it replaces `registry` with `registry-index` so that the - /// manifest contains the correct URL. All users won't have the same - /// registry names configured, so Cargo can't rely on just the name for - /// crates published by other users. - registry_index: Option<String>, - // `path` is relative to the file it appears in. If that's a `Cargo.toml`, it'll be relative to - // that TOML file, and if it's a `.cargo/config` file, it'll be relative to that file. - path: Option<P>, - git: Option<String>, - branch: Option<String>, - tag: Option<String>, - rev: Option<String>, - features: Option<Vec<String>>, - optional: Option<bool>, - default_features: Option<bool>, - #[serde(rename = "default_features")] - default_features2: Option<bool>, - package: Option<String>, - public: Option<bool>, - - /// One or more of `bin`, `cdylib`, `staticlib`, `bin:<name>`. - artifact: Option<StringOrVec>, - /// If set, the artifact should also be a dependency - lib: Option<bool>, - /// A platform name, like `x86_64-apple-darwin` - target: Option<String>, - /// This is here to provide a way to see the "unused manifest keys" when deserializing - #[serde(skip_serializing)] - #[serde(flatten)] - other: BTreeMap<String, toml::Value>, -} - -// Explicit implementation so we avoid pulling in P: Default -impl<P: Clone> Default for DetailedTomlDependency<P> { - fn default() -> Self { - Self { - version: Default::default(), - registry: Default::default(), - registry_index: Default::default(), - path: Default::default(), - git: Default::default(), - branch: Default::default(), - tag: Default::default(), - rev: Default::default(), - features: Default::default(), - optional: Default::default(), - default_features: Default::default(), - default_features2: Default::default(), - package: Default::default(), - public: Default::default(), - artifact: Default::default(), - lib: Default::default(), - target: Default::default(), - other: Default::default(), - } - } -} - -/// This type is used to deserialize `Cargo.toml` files. -#[derive(Debug, Deserialize, Serialize)] -#[serde(rename_all = "kebab-case")] -pub struct TomlManifest { - cargo_features: Option<Vec<String>>, - package: Option<Box<TomlPackage>>, - project: Option<Box<TomlPackage>>, - profile: Option<TomlProfiles>, - lib: Option<TomlLibTarget>, - bin: Option<Vec<TomlBinTarget>>, - example: Option<Vec<TomlExampleTarget>>, - test: Option<Vec<TomlTestTarget>>, - bench: Option<Vec<TomlTestTarget>>, - dependencies: Option<BTreeMap<String, MaybeWorkspaceDependency>>, - dev_dependencies: Option<BTreeMap<String, MaybeWorkspaceDependency>>, - #[serde(rename = "dev_dependencies")] - dev_dependencies2: Option<BTreeMap<String, MaybeWorkspaceDependency>>, - build_dependencies: Option<BTreeMap<String, MaybeWorkspaceDependency>>, - #[serde(rename = "build_dependencies")] - build_dependencies2: Option<BTreeMap<String, MaybeWorkspaceDependency>>, - features: Option<BTreeMap<InternedString, Vec<InternedString>>>, - target: Option<BTreeMap<String, TomlPlatform>>, - replace: Option<BTreeMap<String, TomlDependency>>, - patch: Option<BTreeMap<String, BTreeMap<String, TomlDependency>>>, - workspace: Option<TomlWorkspace>, - badges: Option<MaybeWorkspaceBtreeMap>, - lints: Option<MaybeWorkspaceLints>, -} - -#[derive(Deserialize, Serialize, Clone, Debug, Default)] -pub struct TomlProfiles(BTreeMap<InternedString, TomlProfile>); - -impl TomlProfiles { - pub fn get_all(&self) -> &BTreeMap<InternedString, TomlProfile> { - &self.0 - } - - pub fn get(&self, name: &str) -> Option<&TomlProfile> { - self.0.get(name) - } - - /// Checks syntax validity and unstable feature gate for each profile. - /// - /// It's a bit unfortunate both `-Z` flags and `cargo-features` are required, - /// because profiles can now be set in either `Cargo.toml` or `config.toml`. - pub fn validate( - &self, - cli_unstable: &CliUnstable, - features: &Features, - warnings: &mut Vec<String>, - ) -> CargoResult<()> { - for (name, profile) in &self.0 { - profile.validate(name, cli_unstable, features, warnings)?; - } - Ok(()) - } -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct TomlOptLevel(pub String); - -impl<'de> de::Deserialize<'de> for TomlOptLevel { - fn deserialize<D>(d: D) -> Result<TomlOptLevel, D::Error> - where - D: de::Deserializer<'de>, - { - use serde::de::Error as _; - UntaggedEnumVisitor::new() - .expecting("an optimization level") - .i64(|value| Ok(TomlOptLevel(value.to_string()))) - .string(|value| { - if value == "s" || value == "z" { - Ok(TomlOptLevel(value.to_string())) - } else { - Err(serde_untagged::de::Error::custom(format!( - "must be `0`, `1`, `2`, `3`, `s` or `z`, \ - but found the string: \"{}\"", - value - ))) - } - }) - .deserialize(d) - } -} - -impl ser::Serialize for TomlOptLevel { - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where - S: ser::Serializer, - { - match self.0.parse::<u32>() { - Ok(n) => n.serialize(serializer), - Err(_) => self.0.serialize(serializer), - } - } -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)] -pub enum TomlDebugInfo { - None, - LineDirectivesOnly, - LineTablesOnly, - Limited, - Full, -} - -impl ser::Serialize for TomlDebugInfo { - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where - S: ser::Serializer, - { - match self { - Self::None => 0.serialize(serializer), - Self::LineDirectivesOnly => "line-directives-only".serialize(serializer), - Self::LineTablesOnly => "line-tables-only".serialize(serializer), - Self::Limited => 1.serialize(serializer), - Self::Full => 2.serialize(serializer), - } - } -} - -impl<'de> de::Deserialize<'de> for TomlDebugInfo { - fn deserialize<D>(d: D) -> Result<TomlDebugInfo, D::Error> - where - D: de::Deserializer<'de>, - { - use serde::de::Error as _; - let expecting = "a boolean, 0, 1, 2, \"line-tables-only\", or \"line-directives-only\""; - UntaggedEnumVisitor::new() - .expecting(expecting) - .bool(|value| { - Ok(if value { - TomlDebugInfo::Full - } else { - TomlDebugInfo::None - }) - }) - .i64(|value| { - let debuginfo = match value { - 0 => TomlDebugInfo::None, - 1 => TomlDebugInfo::Limited, - 2 => TomlDebugInfo::Full, - _ => { - return Err(serde_untagged::de::Error::invalid_value( - Unexpected::Signed(value), - &expecting, - )) - } - }; - Ok(debuginfo) - }) - .string(|value| { - let debuginfo = match value { - "none" => TomlDebugInfo::None, - "limited" => TomlDebugInfo::Limited, - "full" => TomlDebugInfo::Full, - "line-directives-only" => TomlDebugInfo::LineDirectivesOnly, - "line-tables-only" => TomlDebugInfo::LineTablesOnly, - _ => { - return Err(serde_untagged::de::Error::invalid_value( - Unexpected::Str(value), - &expecting, - )) - } - }; - Ok(debuginfo) - }) - .deserialize(d) - } -} - -impl Display for TomlDebugInfo { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - TomlDebugInfo::None => f.write_char('0'), - TomlDebugInfo::Limited => f.write_char('1'), - TomlDebugInfo::Full => f.write_char('2'), - TomlDebugInfo::LineDirectivesOnly => f.write_str("line-directives-only"), - TomlDebugInfo::LineTablesOnly => f.write_str("line-tables-only"), - } - } -} - -#[derive(Deserialize, Serialize, Clone, Debug, Default, Eq, PartialEq)] -#[serde(default, rename_all = "kebab-case")] -pub struct TomlProfile { - pub opt_level: Option<TomlOptLevel>, - pub lto: Option<StringOrBool>, - pub codegen_backend: Option<InternedString>, - pub codegen_units: Option<u32>, - pub debug: Option<TomlDebugInfo>, - pub split_debuginfo: Option<String>, - pub debug_assertions: Option<bool>, - pub rpath: Option<bool>, - pub panic: Option<String>, - pub overflow_checks: Option<bool>, - pub incremental: Option<bool>, - pub dir_name: Option<InternedString>, - pub inherits: Option<InternedString>, - pub strip: Option<StringOrBool>, - // Note that `rustflags` is used for the cargo-feature `profile_rustflags` - pub rustflags: Option<Vec<InternedString>>, - // These two fields must be last because they are sub-tables, and TOML - // requires all non-tables to be listed first. - pub package: Option<BTreeMap<ProfilePackageSpec, TomlProfile>>, - pub build_override: Option<Box<TomlProfile>>, -} - -#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] -pub enum ProfilePackageSpec { - Spec(PackageIdSpec), - All, -} - -impl ser::Serialize for ProfilePackageSpec { - fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error> - where - S: ser::Serializer, - { - self.to_string().serialize(s) - } -} - -impl<'de> de::Deserialize<'de> for ProfilePackageSpec { - fn deserialize<D>(d: D) -> Result<ProfilePackageSpec, D::Error> - where - D: de::Deserializer<'de>, - { - let string = String::deserialize(d)?; - if string == "*" { - Ok(ProfilePackageSpec::All) - } else { - PackageIdSpec::parse(&string) - .map_err(de::Error::custom) - .map(ProfilePackageSpec::Spec) - } - } -} - -impl fmt::Display for ProfilePackageSpec { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - ProfilePackageSpec::Spec(spec) => spec.fmt(f), - ProfilePackageSpec::All => f.write_str("*"), - } - } -} - -impl TomlProfile { - /// Checks stytax validity and unstable feature gate for a given profile. - pub fn validate( - &self, - name: &str, - cli_unstable: &CliUnstable, - features: &Features, - warnings: &mut Vec<String>, - ) -> CargoResult<()> { - self.validate_profile(name, cli_unstable, features)?; - if let Some(ref profile) = self.build_override { - profile.validate_override("build-override")?; - profile.validate_profile(&format!("{name}.build-override"), cli_unstable, features)?; - } - if let Some(ref packages) = self.package { - for (override_name, profile) in packages { - profile.validate_override("package")?; - profile.validate_profile( - &format!("{name}.package.{override_name}"), - cli_unstable, - features, - )?; - } - } - - // Profile name validation - Self::validate_name(name)?; - - if let Some(dir_name) = self.dir_name { - // This is disabled for now, as we would like to stabilize named - // profiles without this, and then decide in the future if it is - // needed. This helps simplify the UI a little. - bail!( - "dir-name=\"{}\" in profile `{}` is not currently allowed, \ - directory names are tied to the profile name for custom profiles", - dir_name, - name - ); - } - - // `inherits` validation - if matches!(self.inherits.map(|s| s.as_str()), Some("debug")) { - bail!( - "profile.{}.inherits=\"debug\" should be profile.{}.inherits=\"dev\"", - name, - name - ); - } - - match name { - "doc" => { - warnings.push("profile `doc` is deprecated and has no effect".to_string()); - } - "test" | "bench" => { - if self.panic.is_some() { - warnings.push(format!("`panic` setting is ignored for `{}` profile", name)) - } - } - _ => {} - } - - if let Some(panic) = &self.panic { - if panic != "unwind" && panic != "abort" { - bail!( - "`panic` setting of `{}` is not a valid setting, \ - must be `unwind` or `abort`", - panic - ); - } - } - - if let Some(StringOrBool::String(arg)) = &self.lto { - if arg == "true" || arg == "false" { - bail!( - "`lto` setting of string `\"{arg}\"` for `{name}` profile is not \ - a valid setting, must be a boolean (`true`/`false`) or a string \ - (`\"thin\"`/`\"fat\"`/`\"off\"`) or omitted.", - ); - } - } - - Ok(()) - } - - /// Validate dir-names and profile names according to RFC 2678. - pub fn validate_name(name: &str) -> CargoResult<()> { - if let Some(ch) = name - .chars() - .find(|ch| !ch.is_alphanumeric() && *ch != '_' && *ch != '-') - { - bail!( - "invalid character `{}` in profile name `{}`\n\ - Allowed characters are letters, numbers, underscore, and hyphen.", - ch, - name - ); - } - - const SEE_DOCS: &str = "See https://doc.rust-lang.org/cargo/reference/profiles.html \ - for more on configuring profiles."; - - let lower_name = name.to_lowercase(); - if lower_name == "debug" { - bail!( - "profile name `{}` is reserved\n\ - To configure the default development profile, use the name `dev` \ - as in [profile.dev]\n\ - {}", - name, - SEE_DOCS - ); - } - if lower_name == "build-override" { - bail!( - "profile name `{}` is reserved\n\ - To configure build dependency settings, use [profile.dev.build-override] \ - and [profile.release.build-override]\n\ - {}", - name, - SEE_DOCS - ); - } - - // These are some arbitrary reservations. We have no plans to use - // these, but it seems safer to reserve a few just in case we want to - // add more built-in profiles in the future. We can also uses special - // syntax like cargo:foo if needed. But it is unlikely these will ever - // be used. - if matches!( - lower_name.as_str(), - "build" - | "check" - | "clean" - | "config" - | "fetch" - | "fix" - | "install" - | "metadata" - | "package" - | "publish" - | "report" - | "root" - | "run" - | "rust" - | "rustc" - | "rustdoc" - | "target" - | "tmp" - | "uninstall" - ) || lower_name.starts_with("cargo") - { - bail!( - "profile name `{}` is reserved\n\ - Please choose a different name.\n\ - {}", - name, - SEE_DOCS - ); - } - - Ok(()) - } - - /// Validates a profile. - /// - /// This is a shallow check, which is reused for the profile itself and any overrides. - fn validate_profile( - &self, - name: &str, - cli_unstable: &CliUnstable, - features: &Features, - ) -> CargoResult<()> { - if let Some(codegen_backend) = &self.codegen_backend { - match ( - features.require(Feature::codegen_backend()), - cli_unstable.codegen_backend, - ) { - (Err(e), false) => return Err(e), - _ => {} - } - - if codegen_backend.contains(|c: char| !c.is_ascii_alphanumeric() && c != '_') { - bail!( - "`profile.{}.codegen-backend` setting of `{}` is not a valid backend name.", - name, - codegen_backend, - ); - } - } - if self.rustflags.is_some() { - match ( - features.require(Feature::profile_rustflags()), - cli_unstable.profile_rustflags, - ) { - (Err(e), false) => return Err(e), - _ => {} - } - } - Ok(()) - } - - /// Validation that is specific to an override. - fn validate_override(&self, which: &str) -> CargoResult<()> { - if self.package.is_some() { - bail!("package-specific profiles cannot be nested"); - } - if self.build_override.is_some() { - bail!("build-override profiles cannot be nested"); - } - if self.panic.is_some() { - bail!("`panic` may not be specified in a `{}` profile", which) - } - if self.lto.is_some() { - bail!("`lto` may not be specified in a `{}` profile", which) - } - if self.rpath.is_some() { - bail!("`rpath` may not be specified in a `{}` profile", which) - } - Ok(()) - } - - /// Overwrite self's values with the given profile. - pub fn merge(&mut self, profile: &TomlProfile) { - if let Some(v) = &profile.opt_level { - self.opt_level = Some(v.clone()); - } - - if let Some(v) = &profile.lto { - self.lto = Some(v.clone()); - } - - if let Some(v) = profile.codegen_backend { - self.codegen_backend = Some(v); - } - - if let Some(v) = profile.codegen_units { - self.codegen_units = Some(v); - } - - if let Some(v) = profile.debug { - self.debug = Some(v); - } - - if let Some(v) = profile.debug_assertions { - self.debug_assertions = Some(v); - } - - if let Some(v) = &profile.split_debuginfo { - self.split_debuginfo = Some(v.clone()); - } - - if let Some(v) = profile.rpath { - self.rpath = Some(v); - } - - if let Some(v) = &profile.panic { - self.panic = Some(v.clone()); - } - - if let Some(v) = profile.overflow_checks { - self.overflow_checks = Some(v); - } - - if let Some(v) = profile.incremental { - self.incremental = Some(v); - } - - if let Some(v) = &profile.rustflags { - self.rustflags = Some(v.clone()); - } - - if let Some(other_package) = &profile.package { - match &mut self.package { - Some(self_package) => { - for (spec, other_pkg_profile) in other_package { - match self_package.get_mut(spec) { - Some(p) => p.merge(other_pkg_profile), - None => { - self_package.insert(spec.clone(), other_pkg_profile.clone()); - } - } - } - } - None => self.package = Some(other_package.clone()), - } - } - - if let Some(other_bo) = &profile.build_override { - match &mut self.build_override { - Some(self_bo) => self_bo.merge(other_bo), - None => self.build_override = Some(other_bo.clone()), - } - } - - if let Some(v) = &profile.inherits { - self.inherits = Some(*v); - } - - if let Some(v) = &profile.dir_name { - self.dir_name = Some(*v); - } - - if let Some(v) = &profile.strip { - self.strip = Some(v.clone()); - } - } -} - -/// A StringOrVec can be parsed from either a TOML string or array, -/// but is always stored as a vector. -#[derive(Clone, Debug, Serialize, Eq, PartialEq, PartialOrd, Ord)] -pub struct StringOrVec(Vec<String>); - -impl<'de> de::Deserialize<'de> for StringOrVec { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where - D: de::Deserializer<'de>, - { - UntaggedEnumVisitor::new() - .expecting("string or list of strings") - .string(|value| Ok(StringOrVec(vec![value.to_owned()]))) - .seq(|value| value.deserialize().map(StringOrVec)) - .deserialize(deserializer) - } -} - -impl StringOrVec { - pub fn iter<'a>(&'a self) -> std::slice::Iter<'a, String> { - self.0.iter() - } -} - -#[derive(Clone, Debug, Serialize, Eq, PartialEq)] -#[serde(untagged)] -pub enum StringOrBool { - String(String), - Bool(bool), -} - -impl<'de> Deserialize<'de> for StringOrBool { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where - D: de::Deserializer<'de>, - { - UntaggedEnumVisitor::new() - .bool(|b| Ok(StringOrBool::Bool(b))) - .string(|s| Ok(StringOrBool::String(s.to_owned()))) - .deserialize(deserializer) - } -} - -#[derive(PartialEq, Clone, Debug, Serialize)] -#[serde(untagged)] -pub enum VecStringOrBool { - VecString(Vec<String>), - Bool(bool), -} - -impl<'de> de::Deserialize<'de> for VecStringOrBool { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where - D: de::Deserializer<'de>, - { - UntaggedEnumVisitor::new() - .expecting("a boolean or vector of strings") - .bool(|value| Ok(VecStringOrBool::Bool(value))) - .seq(|value| value.deserialize().map(VecStringOrBool::VecString)) - .deserialize(deserializer) - } -} - -fn version_trim_whitespace<'de, D>(deserializer: D) -> Result<MaybeWorkspaceSemverVersion, D::Error> -where - D: de::Deserializer<'de>, -{ - UntaggedEnumVisitor::new() - .expecting("SemVer version") - .string( - |value| match value.trim().parse().map_err(de::Error::custom) { - Ok(parsed) => Ok(MaybeWorkspace::Defined(parsed)), - Err(e) => Err(e), - }, - ) - .map(|value| value.deserialize().map(MaybeWorkspace::Workspace)) - .deserialize(deserializer) -} - -/// This Trait exists to make [`MaybeWorkspace::Workspace`] generic. It makes deserialization of -/// [`MaybeWorkspace`] much easier, as well as making error messages for -/// [`MaybeWorkspace::resolve`] much nicer -/// -/// Implementors should have a field `workspace` with the type of `bool`. It is used to ensure -/// `workspace` is not `false` in a `Cargo.toml` -pub trait WorkspaceInherit { - /// This is the workspace table that is being inherited from. - /// For example `[workspace.dependencies]` would be the table "dependencies" - fn inherit_toml_table(&self) -> &str; - - /// This is used to output the value of the implementors `workspace` field - fn workspace(&self) -> bool; -} - -/// An enum that allows for inheriting keys from a workspace in a Cargo.toml. -#[derive(Serialize, Copy, Clone, Debug)] -#[serde(untagged)] -pub enum MaybeWorkspace<T, W: WorkspaceInherit> { - /// The "defined" type, or the type that that is used when not inheriting from a workspace. - Defined(T), - /// The type when inheriting from a workspace. - Workspace(W), -} - -impl<T, W: WorkspaceInherit> MaybeWorkspace<T, W> { - fn resolve<'a>( - self, - label: &str, - get_ws_inheritable: impl FnOnce() -> CargoResult<T>, - ) -> CargoResult<T> { - match self { - MaybeWorkspace::Defined(value) => Ok(value), - MaybeWorkspace::Workspace(w) => get_ws_inheritable().with_context(|| { - format!( - "error inheriting `{label}` from workspace root manifest's `workspace.{}.{label}`", - w.inherit_toml_table(), - ) - }), - } - } - - fn resolve_with_self<'a>( - self, - label: &str, - get_ws_inheritable: impl FnOnce(&W) -> CargoResult<T>, - ) -> CargoResult<T> { - match self { - MaybeWorkspace::Defined(value) => Ok(value), - MaybeWorkspace::Workspace(w) => get_ws_inheritable(&w).with_context(|| { - format!( - "error inheriting `{label}` from workspace root manifest's `workspace.{}.{label}`", - w.inherit_toml_table(), - ) - }), - } - } - - fn as_defined(&self) -> Option<&T> { - match self { - MaybeWorkspace::Workspace(_) => None, - MaybeWorkspace::Defined(defined) => Some(defined), - } - } -} - -type MaybeWorkspaceDependency = MaybeWorkspace<TomlDependency, TomlWorkspaceDependency>; - -impl<'de> de::Deserialize<'de> for MaybeWorkspaceDependency { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - 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::<D::Error>::new(value)) - .map(MaybeWorkspace::Defined) - } -} - -impl MaybeWorkspaceDependency { - fn unused_keys(&self) -> Vec<String> { - 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 { - workspace: bool, - features: Option<Vec<String>>, - default_features: Option<bool>, - #[serde(rename = "default_features")] - default_features2: Option<bool>, - optional: Option<bool>, - /// This is here to provide a way to see the "unused manifest keys" when deserializing - #[serde(skip_serializing)] - #[serde(flatten)] - other: BTreeMap<String, toml::Value>, -} - -impl WorkspaceInherit for TomlWorkspaceDependency { - fn inherit_toml_table(&self) -> &str { - "dependencies" - } - - fn workspace(&self) -> bool { - self.workspace - } -} - -impl TomlWorkspaceDependency { - fn resolve<'a>( - &self, - name: &str, - inheritable: impl FnOnce() -> CargoResult<&'a InheritableFields>, - cx: &mut Context<'_, '_>, - ) -> CargoResult<TomlDependency> { - fn default_features_msg(label: &str, ws_def_feat: Option<bool>, cx: &mut Context<'_, '_>) { - let ws_def_feat = match ws_def_feat { - Some(true) => "true", - Some(false) => "false", - None => "not specified", - }; - cx.warnings.push(format!( - "`default-features` is ignored for {label}, since `default-features` was \ - {ws_def_feat} for `workspace.dependencies.{label}`, \ - this could become a hard error in the future" - )) - } - if self.default_features.is_some() && self.default_features2.is_some() { - warn_on_deprecated("default-features", name, "dependency", cx.warnings); - } - inheritable()?.get_dependency(name, cx.root).map(|d| { - match d { - TomlDependency::Simple(s) => { - if let Some(false) = self.default_features.or(self.default_features2) { - default_features_msg(name, None, cx); - } - if self.optional.is_some() || self.features.is_some() { - TomlDependency::Detailed(DetailedTomlDependency { - version: Some(s), - optional: self.optional, - features: self.features.clone(), - ..Default::default() - }) - } else { - TomlDependency::Simple(s) - } - } - TomlDependency::Detailed(d) => { - let mut d = d.clone(); - match ( - self.default_features.or(self.default_features2), - d.default_features.or(d.default_features2), - ) { - // member: default-features = true and - // workspace: default-features = false should turn on - // default-features - (Some(true), Some(false)) => { - d.default_features = Some(true); - } - // member: default-features = false and - // workspace: default-features = true should ignore member - // default-features - (Some(false), Some(true)) => { - default_features_msg(name, Some(true), cx); - } - // member: default-features = false and - // workspace: dep = "1.0" should ignore member default-features - (Some(false), None) => { - default_features_msg(name, None, cx); - } - _ => {} - } - d.add_features(self.features.clone()); - d.update_optional(self.optional); - TomlDependency::Detailed(d) - } - } - }) - } -} - -//. This already has a `Deserialize` impl from version_trim_whitespace -type MaybeWorkspaceSemverVersion = MaybeWorkspace<semver::Version, TomlWorkspaceField>; - -type MaybeWorkspaceString = MaybeWorkspace<String, TomlWorkspaceField>; -impl<'de> de::Deserialize<'de> for MaybeWorkspaceString { - fn deserialize<D>(d: D) -> Result<Self, D::Error> - 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<E>(self, value: String) -> Result<Self::Value, E> - where - E: de::Error, - { - Ok(MaybeWorkspaceString::Defined(value)) - } - - fn visit_map<V>(self, map: V) -> Result<Self::Value, V::Error> - where - V: de::MapAccess<'de>, - { - let mvd = de::value::MapAccessDeserializer::new(map); - TomlWorkspaceField::deserialize(mvd).map(MaybeWorkspace::Workspace) - } - } - - d.deserialize_any(Visitor) - } -} - -type MaybeWorkspaceRustVersion = MaybeWorkspace<RustVersion, TomlWorkspaceField>; -impl<'de> de::Deserialize<'de> for MaybeWorkspaceRustVersion { - fn deserialize<D>(d: D) -> Result<Self, D::Error> - where - D: de::Deserializer<'de>, - { - struct Visitor; - - impl<'de> de::Visitor<'de> for Visitor { - type Value = MaybeWorkspaceRustVersion; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - f.write_str("a semver or workspace") - } - - fn visit_string<E>(self, value: String) -> Result<Self::Value, E> - where - E: de::Error, - { - let value = value.parse::<RustVersion>().map_err(|e| E::custom(e))?; - Ok(MaybeWorkspaceRustVersion::Defined(value)) - } - - fn visit_map<V>(self, map: V) -> Result<Self::Value, V::Error> - 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<Vec<String>, TomlWorkspaceField>; -impl<'de> de::Deserialize<'de> for MaybeWorkspaceVecString { - fn deserialize<D>(d: D) -> Result<Self, D::Error> - 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<A>(self, v: A) -> Result<Self::Value, A::Error> - where - A: de::SeqAccess<'de>, - { - let seq = de::value::SeqAccessDeserializer::new(v); - Vec::deserialize(seq).map(MaybeWorkspace::Defined) - } - - fn visit_map<V>(self, map: V) -> Result<Self::Value, V::Error> - 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<StringOrBool, TomlWorkspaceField>; -impl<'de> de::Deserialize<'de> for MaybeWorkspaceStringOrBool { - fn deserialize<D>(d: D) -> Result<Self, D::Error> - 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<E>(self, v: bool) -> Result<Self::Value, E> - where - E: de::Error, - { - let b = de::value::BoolDeserializer::new(v); - StringOrBool::deserialize(b).map(MaybeWorkspace::Defined) - } - - fn visit_string<E>(self, v: String) -> Result<Self::Value, E> - where - E: de::Error, - { - let string = de::value::StringDeserializer::new(v); - StringOrBool::deserialize(string).map(MaybeWorkspace::Defined) - } - - fn visit_map<V>(self, map: V) -> Result<Self::Value, V::Error> - 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<VecStringOrBool, TomlWorkspaceField>; -impl<'de> de::Deserialize<'de> for MaybeWorkspaceVecStringOrBool { - fn deserialize<D>(d: D) -> Result<Self, D::Error> - 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<E>(self, v: bool) -> Result<Self::Value, E> - where - E: de::Error, - { - let b = de::value::BoolDeserializer::new(v); - VecStringOrBool::deserialize(b).map(MaybeWorkspace::Defined) - } - - fn visit_seq<A>(self, v: A) -> Result<Self::Value, A::Error> - where - A: de::SeqAccess<'de>, - { - let seq = de::value::SeqAccessDeserializer::new(v); - VecStringOrBool::deserialize(seq).map(MaybeWorkspace::Defined) - } - - fn visit_map<V>(self, map: V) -> Result<Self::Value, V::Error> - 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<BTreeMap<String, BTreeMap<String, String>>, TomlWorkspaceField>; - -impl<'de> de::Deserialize<'de> for MaybeWorkspaceBtreeMap { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where - D: de::Deserializer<'de>, - { - let value = serde_value::Value::deserialize(deserializer)?; - - if let Ok(w) = TomlWorkspaceField::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")) - }; - } - BTreeMap::deserialize(serde_value::ValueDeserializer::<D::Error>::new(value)) - .map(MaybeWorkspace::Defined) - } -} - -#[derive(Deserialize, Serialize, Copy, 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<bool, D::Error> { - 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" - } - - fn workspace(&self) -> bool { - self.workspace - } -} - -/// Represents the `package`/`project` sections of a `Cargo.toml`. -/// -/// Note that the order of the fields matters, since this is the order they -/// are serialized to a TOML file. For example, you cannot have values after -/// the field `metadata`, since it is a table and values cannot appear after -/// tables. -#[derive(Deserialize, Serialize, Clone, Debug)] -#[serde(rename_all = "kebab-case")] -pub struct TomlPackage { - edition: Option<MaybeWorkspaceString>, - rust_version: Option<MaybeWorkspaceRustVersion>, - name: InternedString, - #[serde(deserialize_with = "version_trim_whitespace")] - version: MaybeWorkspaceSemverVersion, - authors: Option<MaybeWorkspaceVecString>, - build: Option<StringOrBool>, - metabuild: Option<StringOrVec>, - default_target: Option<String>, - forced_target: Option<String>, - links: Option<String>, - exclude: Option<MaybeWorkspaceVecString>, - include: Option<MaybeWorkspaceVecString>, - publish: Option<MaybeWorkspaceVecStringOrBool>, - workspace: Option<String>, - im_a_teapot: Option<bool>, - autobins: Option<bool>, - autoexamples: Option<bool>, - autotests: Option<bool>, - autobenches: Option<bool>, - default_run: Option<String>, - - // Package metadata. - description: Option<MaybeWorkspaceString>, - homepage: Option<MaybeWorkspaceString>, - documentation: Option<MaybeWorkspaceString>, - readme: Option<MaybeWorkspaceStringOrBool>, - keywords: Option<MaybeWorkspaceVecString>, - categories: Option<MaybeWorkspaceVecString>, - license: Option<MaybeWorkspaceString>, - license_file: Option<MaybeWorkspaceString>, - repository: Option<MaybeWorkspaceString>, - resolver: Option<String>, - - // Provide a helpful error message for a common user error. - #[serde(rename = "cargo-features", skip_serializing)] - _invalid_cargo_features: Option<InvalidCargoFeatures>, - - // Note that this field must come last due to the way toml serialization - // works which requires tables to be emitted after all values. - metadata: Option<toml::Value>, -} - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct TomlWorkspace { - members: Option<Vec<String>>, - #[serde(rename = "default-members")] - default_members: Option<Vec<String>>, - exclude: Option<Vec<String>>, - resolver: Option<String>, - - // Properties that can be inherited by members. - package: Option<InheritableFields>, - dependencies: Option<BTreeMap<String, TomlDependency>>, - lints: Option<TomlLints>, - - // Note that this field must come last due to the way toml serialization - // works which requires tables to be emitted after all values. - metadata: Option<toml::Value>, -} - -/// A group of fields that are inheritable by members of the workspace -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct InheritableFields { - // We use skip here since it will never be present when deserializing - // and we don't want it present when serializing - #[serde(skip)] - dependencies: Option<BTreeMap<String, TomlDependency>>, - #[serde(skip)] - lints: Option<TomlLints>, - - version: Option<semver::Version>, - authors: Option<Vec<String>>, - description: Option<String>, - homepage: Option<String>, - documentation: Option<String>, - readme: Option<StringOrBool>, - keywords: Option<Vec<String>>, - categories: Option<Vec<String>>, - license: Option<String>, - #[serde(rename = "license-file")] - license_file: Option<String>, - repository: Option<String>, - publish: Option<VecStringOrBool>, - edition: Option<String>, - badges: Option<BTreeMap<String, BTreeMap<String, String>>>, - exclude: Option<Vec<String>>, - include: Option<Vec<String>>, - #[serde(rename = "rust-version")] - rust_version: Option<RustVersion>, - // We use skip here since it will never be present when deserializing - // and we don't want it present when serializing - #[serde(skip)] - ws_root: PathBuf, -} - -/// Defines simple getter methods for inheritable fields. -macro_rules! inheritable_field_getter { - ( $(($key:literal, $field:ident -> $ret:ty),)* ) => ( - $( - #[doc = concat!("Gets the field `workspace.", $key, "`.")] - pub fn $field(&self) -> CargoResult<$ret> { - let Some(val) = &self.$field else { - bail!("`workspace.{}` was not defined", $key); - }; - Ok(val.clone()) - } - )* - ) -} - -impl InheritableFields { - inheritable_field_getter! { - // Please keep this list lexicographically ordered. - ("dependencies", dependencies -> BTreeMap<String, TomlDependency>), - ("lints", lints -> TomlLints), - ("package.authors", authors -> Vec<String>), - ("package.badges", badges -> BTreeMap<String, BTreeMap<String, String>>), - ("package.categories", categories -> Vec<String>), - ("package.description", description -> String), - ("package.documentation", documentation -> String), - ("package.edition", edition -> String), - ("package.exclude", exclude -> Vec<String>), - ("package.homepage", homepage -> String), - ("package.include", include -> Vec<String>), - ("package.keywords", keywords -> Vec<String>), - ("package.license", license -> String), - ("package.publish", publish -> VecStringOrBool), - ("package.repository", repository -> String), - ("package.rust-version", rust_version -> RustVersion), - ("package.version", version -> semver::Version), - } - - /// Gets a workspace dependency with the `name`. - pub fn get_dependency(&self, name: &str, package_root: &Path) -> CargoResult<TomlDependency> { - let Some(deps) = &self.dependencies else { - bail!("`workspace.dependencies` was not defined"); - }; - let Some(dep) = deps.get(name) else { - bail!("`dependency.{name}` was not found in `workspace.dependencies`"); - }; - let mut dep = dep.clone(); - if let TomlDependency::Detailed(detailed) = &mut dep { - detailed.resolve_path(name, self.ws_root(), package_root)?; - } - Ok(dep) - } - - /// Gets the field `workspace.package.license-file`. - pub fn license_file(&self, package_root: &Path) -> CargoResult<String> { - let Some(license_file) = &self.license_file else { - bail!("`workspace.package.license-file` was not defined"); - }; - resolve_relative_path("license-file", &self.ws_root, package_root, license_file) - } - - /// Gets the field `workspace.package.readme`. - pub fn readme(&self, package_root: &Path) -> CargoResult<StringOrBool> { - let Some(readme) = readme_for_package(self.ws_root.as_path(), self.readme.as_ref()) else { - bail!("`workspace.package.readme` was not defined"); - }; - resolve_relative_path("readme", &self.ws_root, package_root, &readme) - .map(StringOrBool::String) - } - - pub fn ws_root(&self) -> &PathBuf { - &self.ws_root - } - - pub fn update_deps(&mut self, deps: Option<BTreeMap<String, TomlDependency>>) { - self.dependencies = deps; - } - - pub fn update_lints(&mut self, lints: Option<TomlLints>) { - self.lints = lints; - } - - pub fn update_ws_path(&mut self, ws_root: PathBuf) { - self.ws_root = ws_root; - } -} - -impl TomlPackage { - pub fn to_package_id( - &self, - source_id: SourceId, - version: semver::Version, - ) -> CargoResult<PackageId> { - PackageId::new(self.name, version, source_id) - } -} - -struct Context<'a, 'b> { - deps: &'a mut Vec<Dependency>, - source_id: SourceId, - nested_paths: &'a mut Vec<PathBuf>, - config: &'b Config, - warnings: &'a mut Vec<String>, - platform: Option<Platform>, - root: &'a Path, - features: &'a Features, -} - -impl TomlManifest { +impl schema::TomlManifest { /// Prepares the manifest for publishing. // - Path and git components of dependency specifications are removed. // - License path is updated to point within the package. @@ -1610,7 +192,7 @@ impl TomlManifest { &self, ws: &Workspace<'_>, package_root: &Path, - ) -> CargoResult<TomlManifest> { + ) -> CargoResult<schema::TomlManifest> { let config = ws.config(); let mut package = self .package @@ -1648,7 +230,7 @@ impl TomlManifest { if abs_license_path.strip_prefix(package_root).is_err() { // This path points outside of the package root. `cargo package` // will copy it into the root, so adjust the path to this location. - package.license_file = Some(MaybeWorkspace::Defined( + package.license_file = Some(schema::MaybeWorkspace::Defined( license_path .file_name() .unwrap() @@ -1664,27 +246,29 @@ impl TomlManifest { .as_defined() .context("readme should have been resolved before `prepare_for_publish()`")?; match readme { - StringOrBool::String(readme) => { + schema::StringOrBool::String(readme) => { let readme_path = Path::new(&readme); let abs_readme_path = paths::normalize_path(&package_root.join(readme_path)); if abs_readme_path.strip_prefix(package_root).is_err() { // This path points outside of the package root. `cargo package` // will copy it into the root, so adjust the path to this location. - package.readme = Some(MaybeWorkspace::Defined(StringOrBool::String( - readme_path - .file_name() - .unwrap() - .to_str() - .unwrap() - .to_string(), - ))); + package.readme = Some(schema::MaybeWorkspace::Defined( + schema::StringOrBool::String( + readme_path + .file_name() + .unwrap() + .to_str() + .unwrap() + .to_string(), + ), + )); } } - StringOrBool::Bool(_) => {} + schema::StringOrBool::Bool(_) => {} } } - let all = |_d: &TomlDependency| true; - return Ok(TomlManifest { + let all = |_d: &schema::TomlDependency| true; + return Ok(schema::TomlManifest { package: Some(package), project: None, profile: self.profile.clone(), @@ -1696,19 +280,11 @@ impl TomlManifest { dependencies: map_deps(config, self.dependencies.as_ref(), all)?, dev_dependencies: map_deps( config, - self.dev_dependencies - .as_ref() - .or_else(|| self.dev_dependencies2.as_ref()), - TomlDependency::is_version_specified, + self.dev_dependencies(), + schema::TomlDependency::is_version_specified, )?, dev_dependencies2: None, - build_dependencies: map_deps( - config, - self.build_dependencies - .as_ref() - .or_else(|| self.build_dependencies2.as_ref()), - all, - )?, + build_dependencies: map_deps(config, self.build_dependencies(), all)?, build_dependencies2: None, features: self.features.clone(), target: match self.target.as_ref().map(|target_map| { @@ -1717,23 +293,15 @@ impl TomlManifest { .map(|(k, v)| { Ok(( k.clone(), - TomlPlatform { + schema::TomlPlatform { dependencies: map_deps(config, v.dependencies.as_ref(), all)?, dev_dependencies: map_deps( config, - v.dev_dependencies - .as_ref() - .or_else(|| v.dev_dependencies2.as_ref()), - TomlDependency::is_version_specified, + v.dev_dependencies(), + schema::TomlDependency::is_version_specified, )?, dev_dependencies2: None, - build_dependencies: map_deps( - config, - v.build_dependencies - .as_ref() - .or_else(|| v.build_dependencies2.as_ref()), - all, - )?, + build_dependencies: map_deps(config, v.build_dependencies(), all)?, build_dependencies2: None, }, )) @@ -1754,14 +322,14 @@ impl TomlManifest { fn map_deps( config: &Config, - deps: Option<&BTreeMap<String, MaybeWorkspaceDependency>>, - filter: impl Fn(&TomlDependency) -> bool, - ) -> CargoResult<Option<BTreeMap<String, MaybeWorkspaceDependency>>> { + deps: Option<&BTreeMap<String, schema::MaybeWorkspaceDependency>>, + filter: impl Fn(&schema::TomlDependency) -> bool, + ) -> CargoResult<Option<BTreeMap<String, schema::MaybeWorkspaceDependency>>> { let Some(deps) = deps else { return Ok(None) }; let deps = deps .iter() .filter(|(_k, v)| { - if let MaybeWorkspace::Defined(def) = v { + if let schema::MaybeWorkspace::Defined(def) = v { filter(def) } else { false @@ -1774,10 +342,10 @@ impl TomlManifest { fn map_dependency( config: &Config, - dep: &MaybeWorkspaceDependency, - ) -> CargoResult<MaybeWorkspaceDependency> { + dep: &schema::MaybeWorkspaceDependency, + ) -> CargoResult<schema::MaybeWorkspaceDependency> { let dep = match dep { - MaybeWorkspace::Defined(TomlDependency::Detailed(d)) => { + schema::MaybeWorkspace::Defined(schema::TomlDependency::Detailed(d)) => { let mut d = d.clone(); // Path dependencies become crates.io deps. d.path.take(); @@ -1792,19 +360,21 @@ impl TomlManifest { } Ok(d) } - MaybeWorkspace::Defined(TomlDependency::Simple(s)) => Ok(DetailedTomlDependency { - version: Some(s.clone()), - ..Default::default() - }), + schema::MaybeWorkspace::Defined(schema::TomlDependency::Simple(s)) => { + Ok(schema::DetailedTomlDependency { + version: Some(s.clone()), + ..Default::default() + }) + } _ => unreachable!(), }; - dep.map(TomlDependency::Detailed) - .map(MaybeWorkspace::Defined) + dep.map(schema::TomlDependency::Detailed) + .map(schema::MaybeWorkspace::Defined) } } pub fn to_real_manifest( - me: &Rc<TomlManifest>, + me: schema::TomlManifest, embedded: bool, source_id: SourceId, package_root: &Path, @@ -1814,7 +384,7 @@ impl TomlManifest { config: &Config, resolved_path: &Path, workspace_config: &WorkspaceConfig, - ) -> CargoResult<InheritableFields> { + ) -> CargoResult<schema::InheritableFields> { match workspace_config { WorkspaceConfig::Root(root) => Ok(root.inheritable().clone()), WorkspaceConfig::Member { @@ -1928,25 +498,31 @@ impl TomlManifest { let resolved_path = package_root.join("Cargo.toml"); - let inherit_cell: LazyCell<InheritableFields> = LazyCell::new(); + let inherit_cell: LazyCell<schema::InheritableFields> = LazyCell::new(); let inherit = || inherit_cell.try_borrow_with(|| get_ws(config, &resolved_path, &workspace_config)); let version = package .version .clone() - .resolve("version", || inherit()?.version())?; + .map(|version| version.resolve("version", || inherit()?.version())) + .transpose()?; - package.version = MaybeWorkspace::Defined(version.clone()); + package.version = version.clone().map(schema::MaybeWorkspace::Defined); - let pkgid = package.to_package_id(source_id, version)?; + let pkgid = package.to_package_id( + source_id, + version + .clone() + .unwrap_or_else(|| semver::Version::new(0, 0, 0)), + ); let edition = if let Some(edition) = package.edition.clone() { let edition: Edition = edition .resolve("edition", || inherit()?.edition())? .parse() .with_context(|| "failed to parse the `edition` key")?; - package.edition = Some(MaybeWorkspace::Defined(edition.to_string())); + package.edition = Some(schema::MaybeWorkspace::Defined(edition.to_string())); edition } else { Edition::Edition2015 @@ -1954,10 +530,12 @@ impl TomlManifest { // Add these lines if start a new unstable edition. // ``` // if edition == Edition::Edition20xx { - // features.require(Feature::edition20xx))?; + // features.require(Feature::edition20xx())?; // } // ``` - if !edition.is_stable() { + if edition == Edition::Edition2024 { + features.require(Feature::edition2024())?; + } else if !edition.is_stable() { // Guard in case someone forgets to add .require() return Err(util::errors::internal(format!( "edition {} should be gated", @@ -1969,7 +547,7 @@ impl TomlManifest { let rust_version = rust_version .clone() .resolve("rust_version", || inherit()?.rust_version())?; - let req = rust_version.caret_req(); + let req = rust_version.to_caret_req(); if let Some(first_version) = edition.first_version() { let unsupported = semver::Version::new(first_version.major, first_version.minor - 1, 9999); @@ -2008,7 +586,7 @@ impl TomlManifest { // If we have a lib with no path, use the inferred lib or else the package name. let targets = targets( &features, - me, + &me, package_name, package_root, edition, @@ -2068,11 +646,11 @@ impl TomlManifest { fn process_dependencies( cx: &mut Context<'_, '_>, - new_deps: Option<&BTreeMap<String, MaybeWorkspaceDependency>>, + new_deps: Option<&BTreeMap<String, schema::MaybeWorkspaceDependency>>, kind: Option<DepKind>, workspace_config: &WorkspaceConfig, - inherit_cell: &LazyCell<InheritableFields>, - ) -> CargoResult<Option<BTreeMap<String, MaybeWorkspaceDependency>>> { + inherit_cell: &LazyCell<schema::InheritableFields>, + ) -> CargoResult<Option<BTreeMap<String, schema::MaybeWorkspaceDependency>>> { let Some(dependencies) = new_deps else { return Ok(None); }; @@ -2083,7 +661,7 @@ impl TomlManifest { }) }; - let mut deps: BTreeMap<String, MaybeWorkspaceDependency> = BTreeMap::new(); + let mut deps: BTreeMap<String, schema::MaybeWorkspaceDependency> = BTreeMap::new(); for (n, v) in dependencies.iter() { let resolved = v .clone() @@ -2102,7 +680,10 @@ impl TomlManifest { }; 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())); + deps.insert( + n.to_string(), + schema::MaybeWorkspace::Defined(resolved.clone()), + ); } Ok(Some(deps)) } @@ -2118,10 +699,7 @@ impl TomlManifest { if me.dev_dependencies.is_some() && me.dev_dependencies2.is_some() { warn_on_deprecated("dev-dependencies", package_name, "package", cx.warnings); } - let dev_deps = me - .dev_dependencies - .as_ref() - .or_else(|| me.dev_dependencies2.as_ref()); + let dev_deps = me.dev_dependencies(); let dev_deps = process_dependencies( &mut cx, dev_deps, @@ -2132,10 +710,7 @@ impl TomlManifest { if me.build_dependencies.is_some() && me.build_dependencies2.is_some() { warn_on_deprecated("build-dependencies", package_name, "package", cx.warnings); } - let build_deps = me - .build_dependencies - .as_ref() - .or_else(|| me.build_dependencies2.as_ref()); + let build_deps = me.build_dependencies(); let build_deps = process_dependencies( &mut cx, build_deps, @@ -2150,10 +725,10 @@ impl TomlManifest { .map(|mw| mw.resolve(|| inherit()?.lints())) .transpose()?; let lints = verify_lints(lints)?; - let default = TomlLints::default(); + let default = schema::TomlLints::default(); let rustflags = lints_to_rustflags(lints.as_ref().unwrap_or(&default)); - let mut target: BTreeMap<String, TomlPlatform> = BTreeMap::new(); + let mut target: BTreeMap<String, schema::TomlPlatform> = BTreeMap::new(); for (name, platform) in me.target.iter().flatten() { cx.platform = { let platform: Platform = name.parse()?; @@ -2170,10 +745,7 @@ impl TomlManifest { if platform.build_dependencies.is_some() && platform.build_dependencies2.is_some() { warn_on_deprecated("build-dependencies", name, "platform target", cx.warnings); } - let build_deps = platform - .build_dependencies - .as_ref() - .or_else(|| platform.build_dependencies2.as_ref()); + let build_deps = platform.build_dependencies(); let build_deps = process_dependencies( &mut cx, build_deps, @@ -2184,10 +756,7 @@ impl TomlManifest { if platform.dev_dependencies.is_some() && platform.dev_dependencies2.is_some() { warn_on_deprecated("dev-dependencies", name, "platform target", cx.warnings); } - let dev_deps = platform - .dev_dependencies - .as_ref() - .or_else(|| platform.dev_dependencies2.as_ref()); + let dev_deps = platform.dev_dependencies(); let dev_deps = process_dependencies( &mut cx, dev_deps, @@ -2197,7 +766,7 @@ impl TomlManifest { )?; target.insert( name.clone(), - TomlPlatform { + schema::TomlPlatform { dependencies: deps, build_dependencies: build_deps, build_dependencies2: None, @@ -2248,7 +817,17 @@ impl TomlManifest { let summary = Summary::new( pkgid, deps, - me.features.as_ref().unwrap_or(&empty_features), + &me.features + .as_ref() + .unwrap_or(&empty_features) + .iter() + .map(|(k, v)| { + ( + InternedString::new(k), + v.iter().map(InternedString::from).collect(), + ) + }) + .collect(), package.links.as_deref(), rust_version.clone(), )?; @@ -2326,52 +905,54 @@ impl TomlManifest { package.description = metadata .description .clone() - .map(|description| MaybeWorkspace::Defined(description)); + .map(|description| schema::MaybeWorkspace::Defined(description)); package.homepage = metadata .homepage .clone() - .map(|homepage| MaybeWorkspace::Defined(homepage)); + .map(|homepage| schema::MaybeWorkspace::Defined(homepage)); package.documentation = metadata .documentation .clone() - .map(|documentation| MaybeWorkspace::Defined(documentation)); + .map(|documentation| schema::MaybeWorkspace::Defined(documentation)); package.readme = metadata .readme .clone() - .map(|readme| MaybeWorkspace::Defined(StringOrBool::String(readme))); + .map(|readme| schema::MaybeWorkspace::Defined(schema::StringOrBool::String(readme))); package.authors = package .authors .as_ref() - .map(|_| MaybeWorkspace::Defined(metadata.authors.clone())); + .map(|_| schema::MaybeWorkspace::Defined(metadata.authors.clone())); package.license = metadata .license .clone() - .map(|license| MaybeWorkspace::Defined(license)); + .map(|license| schema::MaybeWorkspace::Defined(license)); package.license_file = metadata .license_file .clone() - .map(|license_file| MaybeWorkspace::Defined(license_file)); + .map(|license_file| schema::MaybeWorkspace::Defined(license_file)); package.repository = metadata .repository .clone() - .map(|repository| MaybeWorkspace::Defined(repository)); + .map(|repository| schema::MaybeWorkspace::Defined(repository)); package.keywords = package .keywords .as_ref() - .map(|_| MaybeWorkspace::Defined(metadata.keywords.clone())); + .map(|_| schema::MaybeWorkspace::Defined(metadata.keywords.clone())); package.categories = package .categories .as_ref() - .map(|_| MaybeWorkspace::Defined(metadata.categories.clone())); - package.rust_version = rust_version.clone().map(|rv| MaybeWorkspace::Defined(rv)); + .map(|_| schema::MaybeWorkspace::Defined(metadata.categories.clone())); + package.rust_version = rust_version + .clone() + .map(|rv| schema::MaybeWorkspace::Defined(rv)); package.exclude = package .exclude .as_ref() - .map(|_| MaybeWorkspace::Defined(exclude.clone())); + .map(|_| schema::MaybeWorkspace::Defined(exclude.clone())); package.include = package .include .as_ref() - .map(|_| MaybeWorkspace::Defined(include.clone())); + .map(|_| schema::MaybeWorkspace::Defined(include.clone())); let profiles = me.profile.clone(); if let Some(profiles) = &profiles { @@ -2384,14 +965,19 @@ impl TomlManifest { .clone() .map(|publish| publish.resolve("publish", || inherit()?.publish()).unwrap()); - package.publish = publish.clone().map(|p| MaybeWorkspace::Defined(p)); + package.publish = publish.clone().map(|p| schema::MaybeWorkspace::Defined(p)); let publish = match publish { - Some(VecStringOrBool::VecString(ref vecstring)) => Some(vecstring.clone()), - Some(VecStringOrBool::Bool(false)) => Some(vec![]), - None | Some(VecStringOrBool::Bool(true)) => None, + Some(schema::VecStringOrBool::VecString(ref vecstring)) => Some(vecstring.clone()), + Some(schema::VecStringOrBool::Bool(false)) => Some(vec![]), + Some(schema::VecStringOrBool::Bool(true)) => None, + None => version.is_none().then_some(vec![]), }; + if version.is_none() && publish != Some(vec![]) { + bail!("`package.publish` requires `package.version` be specified"); + } + if summary.features().contains_key("default-features") { warnings.push( "`default-features = [\"..\"]` was found in [features]. \ @@ -2425,7 +1011,7 @@ impl TomlManifest { .transpose()? .map(CompileKind::Target); let custom_metadata = package.metadata.clone(); - let resolved_toml = TomlManifest { + let resolved_toml = schema::TomlManifest { cargo_features: me.cargo_features.clone(), package: Some(package.clone()), project: None, @@ -2448,8 +1034,8 @@ impl TomlManifest { badges: me .badges .as_ref() - .map(|_| MaybeWorkspace::Defined(metadata.badges.clone())), - lints: lints.map(|lints| MaybeWorkspaceLints { + .map(|_| schema::MaybeWorkspace::Defined(metadata.badges.clone())), + lints: lints.map(|lints| schema::MaybeWorkspaceLints { workspace: false, lints, }), @@ -2504,7 +1090,7 @@ impl TomlManifest { } fn to_virtual_manifest( - me: &Rc<TomlManifest>, + me: schema::TomlManifest, source_id: SourceId, root: &Path, config: &Config, @@ -2533,10 +1119,10 @@ impl TomlManifest { if me.dependencies.is_some() { bail!("this virtual manifest specifies a [dependencies] section, which is not allowed"); } - if me.dev_dependencies.is_some() || me.dev_dependencies2.is_some() { + if me.dev_dependencies().is_some() { bail!("this virtual manifest specifies a [dev-dependencies] section, which is not allowed"); } - if me.build_dependencies.is_some() || me.build_dependencies2.is_some() { + if me.build_dependencies().is_some() { bail!("this virtual manifest specifies a [build-dependencies] section, which is not allowed"); } if me.features.is_some() { @@ -2643,7 +1229,7 @@ impl TomlManifest { ); } - let mut dep = replacement.to_dependency(spec.name().as_str(), cx, None)?; + let mut dep = replacement.to_dependency(spec.name(), cx, None)?; let version = spec.version().ok_or_else(|| { anyhow!( "replacements must specify a version \ @@ -2657,8 +1243,7 @@ impl TomlManifest { replacement.unused_keys(), &mut cx.warnings, ); - dep.set_version_req(OptVersionReq::exact(&version)) - .lock_version(&version); + dep.set_version_req(OptVersionReq::exact(&version)); replace.push((spec, dep)); } Ok(replace) @@ -2697,41 +1282,20 @@ impl TomlManifest { } Ok(patch) } +} - /// Returns the path to the build script if one exists for this crate. - fn maybe_custom_build( - &self, - build: &Option<StringOrBool>, - package_root: &Path, - ) -> Option<PathBuf> { - let build_rs = package_root.join("build.rs"); - match *build { - // Explicitly no build script. - Some(StringOrBool::Bool(false)) => None, - Some(StringOrBool::Bool(true)) => Some(build_rs), - Some(StringOrBool::String(ref s)) => Some(PathBuf::from(s)), - None => { - // If there is a `build.rs` file next to the `Cargo.toml`, assume it is - // a build script. - if build_rs.is_file() { - Some(build_rs) - } else { - None - } - } - } - } - - pub fn has_profiles(&self) -> bool { - self.profile.is_some() - } - - pub fn features(&self) -> Option<&BTreeMap<InternedString, Vec<InternedString>>> { - self.features.as_ref() - } +struct Context<'a, 'b> { + deps: &'a mut Vec<Dependency>, + source_id: SourceId, + nested_paths: &'a mut Vec<PathBuf>, + config: &'b Config, + warnings: &'a mut Vec<String>, + platform: Option<Platform>, + root: &'a Path, + features: &'a Features, } -fn verify_lints(lints: Option<TomlLints>) -> CargoResult<Option<TomlLints>> { +fn verify_lints(lints: Option<schema::TomlLints>) -> CargoResult<Option<schema::TomlLints>> { let Some(lints) = lints else { return Ok(None); }; @@ -2762,7 +1326,7 @@ fn verify_lints(lints: Option<TomlLints>) -> CargoResult<Option<TomlLints>> { Ok(Some(lints)) } -fn lints_to_rustflags(lints: &TomlLints) -> Vec<String> { +fn lints_to_rustflags(lints: &schema::TomlLints) -> Vec<String> { let mut rustflags = lints .iter() .flat_map(|(tool, lints)| { @@ -2802,7 +1366,7 @@ fn unused_dep_keys( fn inheritable_from_path( config: &Config, workspace_path: PathBuf, -) -> CargoResult<InheritableFields> { +) -> CargoResult<schema::InheritableFields> { // Workspace path should have Cargo.toml at the end let workspace_path_root = workspace_path.parent().unwrap(); @@ -2829,14 +1393,17 @@ fn inheritable_from_path( } } -/// Returns the name of the README file for a [`TomlPackage`]. -pub fn readme_for_package(package_root: &Path, readme: Option<&StringOrBool>) -> Option<String> { +/// Returns the name of the README file for a [`schema::TomlPackage`]. +fn readme_for_package( + package_root: &Path, + readme: Option<&schema::StringOrBool>, +) -> Option<String> { match &readme { None => default_readme_from_package_root(package_root), Some(value) => match value { - StringOrBool::Bool(false) => None, - StringOrBool::Bool(true) => Some("README.md".to_string()), - StringOrBool::String(v) => Some(v.clone()), + schema::StringOrBool::Bool(false) => None, + schema::StringOrBool::Bool(true) => Some("README.md".to_string()), + schema::StringOrBool::String(v) => Some(v.clone()), }, } } @@ -2881,7 +1448,254 @@ fn unique_build_targets( Ok(()) } -impl<P: ResolveToPath + Clone> TomlDependency<P> { +/// Defines simple getter methods for inheritable fields. +macro_rules! inheritable_field_getter { + ( $(($key:literal, $field:ident -> $ret:ty),)* ) => ( + $( + #[doc = concat!("Gets the field `workspace.", $key, "`.")] + fn $field(&self) -> CargoResult<$ret> { + let Some(val) = &self.$field else { + bail!("`workspace.{}` was not defined", $key); + }; + Ok(val.clone()) + } + )* + ) +} + +impl schema::InheritableFields { + inheritable_field_getter! { + // Please keep this list lexicographically ordered. + ("lints", lints -> schema::TomlLints), + ("package.authors", authors -> Vec<String>), + ("package.badges", badges -> BTreeMap<String, BTreeMap<String, String>>), + ("package.categories", categories -> Vec<String>), + ("package.description", description -> String), + ("package.documentation", documentation -> String), + ("package.edition", edition -> String), + ("package.exclude", exclude -> Vec<String>), + ("package.homepage", homepage -> String), + ("package.include", include -> Vec<String>), + ("package.keywords", keywords -> Vec<String>), + ("package.license", license -> String), + ("package.publish", publish -> schema::VecStringOrBool), + ("package.repository", repository -> String), + ("package.rust-version", rust_version -> RustVersion), + ("package.version", version -> semver::Version), + } + + /// Gets a workspace dependency with the `name`. + fn get_dependency( + &self, + name: &str, + package_root: &Path, + ) -> CargoResult<schema::TomlDependency> { + let Some(deps) = &self.dependencies else { + bail!("`workspace.dependencies` was not defined"); + }; + let Some(dep) = deps.get(name) else { + bail!("`dependency.{name}` was not found in `workspace.dependencies`"); + }; + let mut dep = dep.clone(); + if let schema::TomlDependency::Detailed(detailed) = &mut dep { + detailed.resolve_path(name, self.ws_root(), package_root)?; + } + Ok(dep) + } + + /// Gets the field `workspace.package.license-file`. + fn license_file(&self, package_root: &Path) -> CargoResult<String> { + let Some(license_file) = &self.license_file else { + bail!("`workspace.package.license-file` was not defined"); + }; + resolve_relative_path("license-file", &self.ws_root, package_root, license_file) + } + + /// Gets the field `workspace.package.readme`. + fn readme(&self, package_root: &Path) -> CargoResult<schema::StringOrBool> { + let Some(readme) = readme_for_package(self.ws_root.as_path(), self.readme.as_ref()) else { + bail!("`workspace.package.readme` was not defined"); + }; + resolve_relative_path("readme", &self.ws_root, package_root, &readme) + .map(schema::StringOrBool::String) + } + + fn ws_root(&self) -> &PathBuf { + &self.ws_root + } + + fn update_deps(&mut self, deps: Option<BTreeMap<String, schema::TomlDependency>>) { + self.dependencies = deps; + } + + fn update_lints(&mut self, lints: Option<schema::TomlLints>) { + self.lints = lints; + } + + fn update_ws_path(&mut self, ws_root: PathBuf) { + self.ws_root = ws_root; + } +} + +impl schema::TomlPackage { + fn to_package_id(&self, source_id: SourceId, version: semver::Version) -> PackageId { + PackageId::pure(self.name.as_str().into(), version, source_id) + } +} + +/// This Trait exists to make [`schema::MaybeWorkspace::Workspace`] generic. It makes deserialization of +/// [`schema::MaybeWorkspace`] much easier, as well as making error messages for +/// [`schema::MaybeWorkspace::resolve`] much nicer +/// +/// Implementors should have a field `workspace` with the type of `bool`. It is used to ensure +/// `workspace` is not `false` in a `Cargo.toml` +pub trait WorkspaceInherit { + /// This is the workspace table that is being inherited from. + /// For example `[workspace.dependencies]` would be the table "dependencies" + fn inherit_toml_table(&self) -> &str; + + /// This is used to output the value of the implementors `workspace` field + fn workspace(&self) -> bool; +} + +impl<T, W: WorkspaceInherit> schema::MaybeWorkspace<T, W> { + fn resolve<'a>( + self, + label: &str, + get_ws_inheritable: impl FnOnce() -> CargoResult<T>, + ) -> CargoResult<T> { + match self { + schema::MaybeWorkspace::Defined(value) => Ok(value), + schema::MaybeWorkspace::Workspace(w) => get_ws_inheritable().with_context(|| { + format!( + "error inheriting `{label}` from workspace root manifest's `workspace.{}.{label}`", + w.inherit_toml_table(), + ) + }), + } + } + + fn resolve_with_self<'a>( + self, + label: &str, + get_ws_inheritable: impl FnOnce(&W) -> CargoResult<T>, + ) -> CargoResult<T> { + match self { + schema::MaybeWorkspace::Defined(value) => Ok(value), + schema::MaybeWorkspace::Workspace(w) => get_ws_inheritable(&w).with_context(|| { + format!( + "error inheriting `{label}` from workspace root manifest's `workspace.{}.{label}`", + w.inherit_toml_table(), + ) + }), + } + } + + fn as_defined(&self) -> Option<&T> { + match self { + schema::MaybeWorkspace::Workspace(_) => None, + schema::MaybeWorkspace::Defined(defined) => Some(defined), + } + } +} + +impl WorkspaceInherit for schema::TomlWorkspaceField { + fn inherit_toml_table(&self) -> &str { + "package" + } + + fn workspace(&self) -> bool { + self.workspace + } +} + +impl schema::TomlWorkspaceDependency { + fn resolve<'a>( + &self, + name: &str, + inheritable: impl FnOnce() -> CargoResult<&'a schema::InheritableFields>, + cx: &mut Context<'_, '_>, + ) -> CargoResult<schema::TomlDependency> { + fn default_features_msg(label: &str, ws_def_feat: Option<bool>, cx: &mut Context<'_, '_>) { + let ws_def_feat = match ws_def_feat { + Some(true) => "true", + Some(false) => "false", + None => "not specified", + }; + cx.warnings.push(format!( + "`default-features` is ignored for {label}, since `default-features` was \ + {ws_def_feat} for `workspace.dependencies.{label}`, \ + this could become a hard error in the future" + )) + } + if self.default_features.is_some() && self.default_features2.is_some() { + warn_on_deprecated("default-features", name, "dependency", cx.warnings); + } + inheritable()?.get_dependency(name, cx.root).map(|d| { + match d { + schema::TomlDependency::Simple(s) => { + if let Some(false) = self.default_features() { + default_features_msg(name, None, cx); + } + if self.optional.is_some() || self.features.is_some() || self.public.is_some() { + schema::TomlDependency::Detailed(schema::DetailedTomlDependency { + version: Some(s), + optional: self.optional, + features: self.features.clone(), + public: self.public, + ..Default::default() + }) + } else { + schema::TomlDependency::Simple(s) + } + } + schema::TomlDependency::Detailed(d) => { + let mut d = d.clone(); + match (self.default_features(), d.default_features()) { + // member: default-features = true and + // workspace: default-features = false should turn on + // default-features + (Some(true), Some(false)) => { + d.default_features = Some(true); + } + // member: default-features = false and + // workspace: default-features = true should ignore member + // default-features + (Some(false), Some(true)) => { + default_features_msg(name, Some(true), cx); + } + // member: default-features = false and + // workspace: dep = "1.0" should ignore member default-features + (Some(false), None) => { + default_features_msg(name, None, cx); + } + _ => {} + } + // Inherit the workspace configuration for `public` unless + // it's explicitly specified for this dependency. + if let Some(public) = self.public { + d.public = Some(public); + } + d.add_features(self.features.clone()); + d.update_optional(self.optional); + schema::TomlDependency::Detailed(d) + } + } + }) + } +} + +impl WorkspaceInherit for schema::TomlWorkspaceDependency { + fn inherit_toml_table(&self) -> &str { + "dependencies" + } + + fn workspace(&self) -> bool { + self.workspace + } +} + +impl<P: ResolveToPath + Clone> schema::TomlDependency<P> { pub(crate) fn to_dependency_split( &self, name: &str, @@ -2917,31 +1731,54 @@ impl<P: ResolveToPath + Clone> TomlDependency<P> { kind: Option<DepKind>, ) -> CargoResult<Dependency> { match *self { - TomlDependency::Simple(ref version) => DetailedTomlDependency::<P> { + schema::TomlDependency::Simple(ref version) => schema::DetailedTomlDependency::<P> { version: Some(version.clone()), ..Default::default() } .to_dependency(name, cx, kind), - TomlDependency::Detailed(ref details) => details.to_dependency(name, cx, kind), + schema::TomlDependency::Detailed(ref details) => details.to_dependency(name, cx, kind), } } +} - fn is_version_specified(&self) -> bool { - match self { - TomlDependency::Detailed(d) => d.version.is_some(), - TomlDependency::Simple(..) => true, - } +impl schema::DetailedTomlDependency { + fn add_features(&mut self, features: Option<Vec<String>>) { + self.features = match (self.features.clone(), features.clone()) { + (Some(dep_feat), Some(inherit_feat)) => Some( + dep_feat + .into_iter() + .chain(inherit_feat) + .collect::<Vec<String>>(), + ), + (Some(dep_fet), None) => Some(dep_fet), + (None, Some(inherit_feat)) => Some(inherit_feat), + (None, None) => None, + }; } - fn is_optional(&self) -> bool { - match self { - TomlDependency::Detailed(d) => d.optional.unwrap_or(false), - TomlDependency::Simple(..) => false, + fn update_optional(&mut self, optional: Option<bool>) { + self.optional = optional; + } + + fn resolve_path( + &mut self, + name: &str, + root_path: &Path, + package_root: &Path, + ) -> CargoResult<()> { + if let Some(rel_path) = &self.path { + self.path = Some(resolve_relative_path( + name, + root_path, + package_root, + rel_path, + )?) } + Ok(()) } } -impl<P: ResolveToPath + Clone> DetailedTomlDependency<P> { +impl<P: ResolveToPath + Clone> schema::DetailedTomlDependency<P> { fn to_dependency( &self, name_in_toml: &str, @@ -3114,11 +1951,7 @@ impl<P: ResolveToPath + Clone> DetailedTomlDependency<P> { warn_on_deprecated("default-features", name_in_toml, "dependency", cx.warnings); } dep.set_features(self.features.iter().flatten()) - .set_default_features( - self.default_features - .or(self.default_features2) - .unwrap_or(true), - ) + .set_default_features(self.default_features().unwrap_or(true)) .set_optional(self.optional.unwrap_or(false)) .set_platform(cx.platform.clone()); if let Some(registry) = &self.registry { @@ -3186,184 +2019,271 @@ impl<P: ResolveToPath + Clone> DetailedTomlDependency<P> { } } -impl DetailedTomlDependency { - fn add_features(&mut self, features: Option<Vec<String>>) { - self.features = match (self.features.clone(), features.clone()) { - (Some(dep_feat), Some(inherit_feat)) => Some( - dep_feat - .into_iter() - .chain(inherit_feat) - .collect::<Vec<String>>(), - ), - (Some(dep_fet), None) => Some(dep_fet), - (None, Some(inherit_feat)) => Some(inherit_feat), - (None, None) => None, - }; - } - - fn update_optional(&mut self, optional: Option<bool>) { - self.optional = optional; +impl schema::TomlProfiles { + /// Checks syntax validity and unstable feature gate for each profile. + /// + /// It's a bit unfortunate both `-Z` flags and `cargo-features` are required, + /// because profiles can now be set in either `Cargo.toml` or `config.toml`. + fn validate( + &self, + cli_unstable: &CliUnstable, + features: &Features, + warnings: &mut Vec<String>, + ) -> CargoResult<()> { + for (name, profile) in &self.0 { + profile.validate(name, cli_unstable, features, warnings)?; + } + Ok(()) } +} - fn resolve_path( - &mut self, +impl schema::TomlProfile { + /// Checks stytax validity and unstable feature gate for a given profile. + pub fn validate( + &self, name: &str, - root_path: &Path, - package_root: &Path, + cli_unstable: &CliUnstable, + features: &Features, + warnings: &mut Vec<String>, ) -> CargoResult<()> { - if let Some(rel_path) = &self.path { - self.path = Some(resolve_relative_path( + self.validate_profile(name, cli_unstable, features)?; + if let Some(ref profile) = self.build_override { + profile.validate_override("build-override")?; + profile.validate_profile(&format!("{name}.build-override"), cli_unstable, features)?; + } + if let Some(ref packages) = self.package { + for (override_name, profile) in packages { + profile.validate_override("package")?; + profile.validate_profile( + &format!("{name}.package.{override_name}"), + cli_unstable, + features, + )?; + } + } + + // Profile name validation + restricted_names::validate_profile_name(name)?; + + if let Some(dir_name) = &self.dir_name { + // This is disabled for now, as we would like to stabilize named + // profiles without this, and then decide in the future if it is + // needed. This helps simplify the UI a little. + bail!( + "dir-name=\"{}\" in profile `{}` is not currently allowed, \ + directory names are tied to the profile name for custom profiles", + dir_name, + name + ); + } + + // `inherits` validation + if matches!(self.inherits.as_deref(), Some("debug")) { + bail!( + "profile.{}.inherits=\"debug\" should be profile.{}.inherits=\"dev\"", name, - root_path, - package_root, - rel_path, - )?) + name + ); } - Ok(()) - } -} -#[derive(Default, Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "kebab-case")] -struct TomlTarget { - name: Option<String>, - - // The intention was to only accept `crate-type` here but historical - // versions of Cargo also accepted `crate_type`, so look for both. - crate_type: Option<Vec<String>>, - #[serde(rename = "crate_type")] - crate_type2: Option<Vec<String>>, - - path: Option<PathValue>, - // Note that `filename` is used for the cargo-feature `different_binary_name` - filename: Option<String>, - test: Option<bool>, - doctest: Option<bool>, - bench: Option<bool>, - doc: Option<bool>, - plugin: Option<bool>, - doc_scrape_examples: Option<bool>, - #[serde(rename = "proc-macro")] - proc_macro_raw: Option<bool>, - #[serde(rename = "proc_macro")] - proc_macro_raw2: Option<bool>, - harness: Option<bool>, - required_features: Option<Vec<String>>, - edition: Option<String>, -} + match name { + "doc" => { + warnings.push("profile `doc` is deprecated and has no effect".to_string()); + } + "test" | "bench" => { + if self.panic.is_some() { + warnings.push(format!("`panic` setting is ignored for `{}` profile", name)) + } + } + _ => {} + } -#[derive(Clone)] -struct PathValue(PathBuf); + if let Some(panic) = &self.panic { + if panic != "unwind" && panic != "abort" { + bail!( + "`panic` setting of `{}` is not a valid setting, \ + must be `unwind` or `abort`", + panic + ); + } + } -impl<'de> de::Deserialize<'de> for PathValue { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where - D: de::Deserializer<'de>, - { - Ok(PathValue(String::deserialize(deserializer)?.into())) - } -} + if let Some(schema::StringOrBool::String(arg)) = &self.lto { + if arg == "true" || arg == "false" { + bail!( + "`lto` setting of string `\"{arg}\"` for `{name}` profile is not \ + a valid setting, must be a boolean (`true`/`false`) or a string \ + (`\"thin\"`/`\"fat\"`/`\"off\"`) or omitted.", + ); + } + } -impl ser::Serialize for PathValue { - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where - S: ser::Serializer, - { - self.0.serialize(serializer) + Ok(()) } -} -/// Corresponds to a `target` entry, but `TomlTarget` is already used. -#[derive(Serialize, Deserialize, Debug, Clone)] -struct TomlPlatform { - dependencies: Option<BTreeMap<String, MaybeWorkspaceDependency>>, - #[serde(rename = "build-dependencies")] - build_dependencies: Option<BTreeMap<String, MaybeWorkspaceDependency>>, - #[serde(rename = "build_dependencies")] - build_dependencies2: Option<BTreeMap<String, MaybeWorkspaceDependency>>, - #[serde(rename = "dev-dependencies")] - dev_dependencies: Option<BTreeMap<String, MaybeWorkspaceDependency>>, - #[serde(rename = "dev_dependencies")] - dev_dependencies2: Option<BTreeMap<String, MaybeWorkspaceDependency>>, -} + /// Validates a profile. + /// + /// This is a shallow check, which is reused for the profile itself and any overrides. + fn validate_profile( + &self, + name: &str, + cli_unstable: &CliUnstable, + features: &Features, + ) -> CargoResult<()> { + if let Some(codegen_backend) = &self.codegen_backend { + match ( + features.require(Feature::codegen_backend()), + cli_unstable.codegen_backend, + ) { + (Err(e), false) => return Err(e), + _ => {} + } -impl TomlTarget { - fn new() -> TomlTarget { - TomlTarget::default() + if codegen_backend.contains(|c: char| !c.is_ascii_alphanumeric() && c != '_') { + bail!( + "`profile.{}.codegen-backend` setting of `{}` is not a valid backend name.", + name, + codegen_backend, + ); + } + } + if self.rustflags.is_some() { + match ( + features.require(Feature::profile_rustflags()), + cli_unstable.profile_rustflags, + ) { + (Err(e), false) => return Err(e), + _ => {} + } + } + if self.trim_paths.is_some() { + match ( + features.require(Feature::trim_paths()), + cli_unstable.trim_paths, + ) { + (Err(e), false) => return Err(e), + _ => {} + } + } + Ok(()) } - fn name(&self) -> String { - match self.name { - Some(ref name) => name.clone(), - None => panic!("target name is required"), + /// Validation that is specific to an override. + fn validate_override(&self, which: &str) -> CargoResult<()> { + if self.package.is_some() { + bail!("package-specific profiles cannot be nested"); + } + if self.build_override.is_some() { + bail!("build-override profiles cannot be nested"); } + if self.panic.is_some() { + bail!("`panic` may not be specified in a `{}` profile", which) + } + if self.lto.is_some() { + bail!("`lto` may not be specified in a `{}` profile", which) + } + if self.rpath.is_some() { + bail!("`rpath` may not be specified in a `{}` profile", which) + } + Ok(()) } - fn validate_proc_macro(&self, warnings: &mut Vec<String>) { - if self.proc_macro_raw.is_some() && self.proc_macro_raw2.is_some() { - warn_on_deprecated( - "proc-macro", - self.name().as_str(), - "library target", - warnings, - ); + /// Overwrite self's values with the given profile. + pub fn merge(&mut self, profile: &schema::TomlProfile) { + if let Some(v) = &profile.opt_level { + self.opt_level = Some(v.clone()); } - } - fn proc_macro(&self) -> Option<bool> { - self.proc_macro_raw.or(self.proc_macro_raw2).or_else(|| { - if let Some(types) = self.crate_types() { - if types.contains(&"proc-macro".to_string()) { - return Some(true); + if let Some(v) = &profile.lto { + self.lto = Some(v.clone()); + } + + if let Some(v) = &profile.codegen_backend { + self.codegen_backend = Some(v.clone()); + } + + if let Some(v) = profile.codegen_units { + self.codegen_units = Some(v); + } + + if let Some(v) = profile.debug { + self.debug = Some(v); + } + + if let Some(v) = profile.debug_assertions { + self.debug_assertions = Some(v); + } + + if let Some(v) = &profile.split_debuginfo { + self.split_debuginfo = Some(v.clone()); + } + + if let Some(v) = profile.rpath { + self.rpath = Some(v); + } + + if let Some(v) = &profile.panic { + self.panic = Some(v.clone()); + } + + if let Some(v) = profile.overflow_checks { + self.overflow_checks = Some(v); + } + + if let Some(v) = profile.incremental { + self.incremental = Some(v); + } + + if let Some(v) = &profile.rustflags { + self.rustflags = Some(v.clone()); + } + + if let Some(other_package) = &profile.package { + match &mut self.package { + Some(self_package) => { + for (spec, other_pkg_profile) in other_package { + match self_package.get_mut(spec) { + Some(p) => p.merge(other_pkg_profile), + None => { + self_package.insert(spec.clone(), other_pkg_profile.clone()); + } + } + } } + None => self.package = Some(other_package.clone()), } - None - }) - } + } - fn validate_crate_types(&self, target_kind_human: &str, warnings: &mut Vec<String>) { - if self.crate_type.is_some() && self.crate_type2.is_some() { - warn_on_deprecated( - "crate-type", - self.name().as_str(), - format!("{target_kind_human} target").as_str(), - warnings, - ); + if let Some(other_bo) = &profile.build_override { + match &mut self.build_override { + Some(self_bo) => self_bo.merge(other_bo), + None => self.build_override = Some(other_bo.clone()), + } } - } - fn crate_types(&self) -> Option<&Vec<String>> { - self.crate_type - .as_ref() - .or_else(|| self.crate_type2.as_ref()) - } -} + if let Some(v) = &profile.inherits { + self.inherits = Some(v.clone()); + } -impl fmt::Debug for PathValue { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} + if let Some(v) = &profile.dir_name { + self.dir_name = Some(v.clone()); + } -#[derive(Deserialize, Serialize, Debug, Clone)] -#[serde(expecting = "a lints table")] -pub struct MaybeWorkspaceLints { - #[serde(skip_serializing_if = "is_false")] - #[serde(deserialize_with = "bool_no_false", default)] - workspace: bool, - #[serde(flatten)] - lints: TomlLints, -} + if let Some(v) = &profile.strip { + self.strip = Some(v.clone()); + } -fn is_false(b: &bool) -> bool { - !b + if let Some(v) = &profile.trim_paths { + self.trim_paths = Some(v.clone()) + } + } } -impl MaybeWorkspaceLints { +impl schema::MaybeWorkspaceLints { fn resolve<'a>( self, - get_ws_inheritable: impl FnOnce() -> CargoResult<TomlLints>, - ) -> CargoResult<TomlLints> { + get_ws_inheritable: impl FnOnce() -> CargoResult<schema::TomlLints>, + ) -> CargoResult<schema::TomlLints> { if self.workspace { if !self.lints.is_empty() { anyhow::bail!("cannot override `workspace.lints` in `lints`, either remove the overrides or `lints.workspace = true` and manually specify the lints"); @@ -3377,65 +2297,7 @@ impl MaybeWorkspaceLints { } } -pub type TomlLints = BTreeMap<String, TomlToolLints>; - -pub type TomlToolLints = BTreeMap<String, TomlLint>; - -#[derive(Serialize, Debug, Clone)] -#[serde(untagged)] -pub enum TomlLint { - Level(TomlLintLevel), - Config(TomlLintConfig), -} - -impl<'de> Deserialize<'de> for TomlLint { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where - D: de::Deserializer<'de>, - { - UntaggedEnumVisitor::new() - .string(|string| { - TomlLintLevel::deserialize(string.into_deserializer()).map(TomlLint::Level) - }) - .map(|map| map.deserialize().map(TomlLint::Config)) - .deserialize(deserializer) - } -} - -impl TomlLint { - fn level(&self) -> TomlLintLevel { - match self { - Self::Level(level) => *level, - Self::Config(config) => config.level, - } - } - - fn priority(&self) -> i8 { - match self { - Self::Level(_) => 0, - Self::Config(config) => config.priority, - } - } -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "kebab-case")] -pub struct TomlLintConfig { - level: TomlLintLevel, - #[serde(default)] - priority: i8, -} - -#[derive(Serialize, Deserialize, Debug, Copy, Clone)] -#[serde(rename_all = "kebab-case")] -pub enum TomlLintLevel { - Forbid, - Deny, - Warn, - Allow, -} - -impl TomlLintLevel { +impl schema::TomlLintLevel { fn flag(&self) -> &'static str { match self { Self::Forbid => "--forbid", @@ -3446,19 +2308,18 @@ impl TomlLintLevel { } } -#[derive(Copy, Clone, Debug)] -#[non_exhaustive] -struct InvalidCargoFeatures {} +pub trait ResolveToPath { + fn resolve(&self, config: &Config) -> PathBuf; +} -impl<'de> de::Deserialize<'de> for InvalidCargoFeatures { - fn deserialize<D>(_d: D) -> Result<Self, D::Error> - where - D: de::Deserializer<'de>, - { - use serde::de::Error as _; +impl ResolveToPath for String { + fn resolve(&self, _: &Config) -> PathBuf { + self.into() + } +} - Err(D::Error::custom( - "the field `cargo-features` should be set at the top of Cargo.toml before any tables", - )) +impl ResolveToPath for ConfigRelativePath { + fn resolve(&self, c: &Config) -> PathBuf { + self.resolve_path(c) } } diff --git a/src/tools/cargo/src/cargo/util/toml/schema.rs b/src/tools/cargo/src/cargo/util/toml/schema.rs new file mode 100644 index 000000000..6ea93e021 --- /dev/null +++ b/src/tools/cargo/src/cargo/util/toml/schema.rs @@ -0,0 +1,1189 @@ +use std::collections::BTreeMap; +use std::fmt::{self, Display, Write}; +use std::path::PathBuf; +use std::str; + +use serde::de::{self, IntoDeserializer as _, Unexpected}; +use serde::ser; +use serde::{Deserialize, Serialize}; +use serde_untagged::UntaggedEnumVisitor; + +use crate::core::PackageIdSpec; +use crate::util::RustVersion; + +/// This type is used to deserialize `Cargo.toml` files. +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +pub struct TomlManifest { + pub cargo_features: Option<Vec<String>>, + pub package: Option<Box<TomlPackage>>, + pub project: Option<Box<TomlPackage>>, + pub profile: Option<TomlProfiles>, + pub lib: Option<TomlLibTarget>, + pub bin: Option<Vec<TomlBinTarget>>, + pub example: Option<Vec<TomlExampleTarget>>, + pub test: Option<Vec<TomlTestTarget>>, + pub bench: Option<Vec<TomlTestTarget>>, + pub dependencies: Option<BTreeMap<String, MaybeWorkspaceDependency>>, + pub dev_dependencies: Option<BTreeMap<String, MaybeWorkspaceDependency>>, + #[serde(rename = "dev_dependencies")] + pub dev_dependencies2: Option<BTreeMap<String, MaybeWorkspaceDependency>>, + pub build_dependencies: Option<BTreeMap<String, MaybeWorkspaceDependency>>, + #[serde(rename = "build_dependencies")] + pub build_dependencies2: Option<BTreeMap<String, MaybeWorkspaceDependency>>, + pub features: Option<BTreeMap<String, Vec<String>>>, + pub target: Option<BTreeMap<String, TomlPlatform>>, + pub replace: Option<BTreeMap<String, TomlDependency>>, + pub patch: Option<BTreeMap<String, BTreeMap<String, TomlDependency>>>, + pub workspace: Option<TomlWorkspace>, + pub badges: Option<MaybeWorkspaceBtreeMap>, + pub lints: Option<MaybeWorkspaceLints>, +} + +impl TomlManifest { + pub fn has_profiles(&self) -> bool { + self.profile.is_some() + } + + pub fn dev_dependencies(&self) -> Option<&BTreeMap<String, MaybeWorkspaceDependency>> { + self.dev_dependencies + .as_ref() + .or(self.dev_dependencies2.as_ref()) + } + + pub fn build_dependencies(&self) -> Option<&BTreeMap<String, MaybeWorkspaceDependency>> { + self.build_dependencies + .as_ref() + .or(self.build_dependencies2.as_ref()) + } + + pub fn features(&self) -> Option<&BTreeMap<String, Vec<String>>> { + self.features.as_ref() + } +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +#[serde(rename_all = "kebab-case")] +pub struct TomlWorkspace { + pub members: Option<Vec<String>>, + pub exclude: Option<Vec<String>>, + pub default_members: Option<Vec<String>>, + pub resolver: Option<String>, + pub metadata: Option<toml::Value>, + + // Properties that can be inherited by members. + pub package: Option<InheritableFields>, + pub dependencies: Option<BTreeMap<String, TomlDependency>>, + pub lints: Option<TomlLints>, +} + +/// A group of fields that are inheritable by members of the workspace +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +pub struct InheritableFields { + // We use skip here since it will never be present when deserializing + // and we don't want it present when serializing + #[serde(skip)] + pub dependencies: Option<BTreeMap<String, TomlDependency>>, + #[serde(skip)] + pub lints: Option<TomlLints>, + + pub version: Option<semver::Version>, + pub authors: Option<Vec<String>>, + pub description: Option<String>, + pub homepage: Option<String>, + pub documentation: Option<String>, + pub readme: Option<StringOrBool>, + pub keywords: Option<Vec<String>>, + pub categories: Option<Vec<String>>, + pub license: Option<String>, + pub license_file: Option<String>, + pub repository: Option<String>, + pub publish: Option<VecStringOrBool>, + pub edition: Option<String>, + pub badges: Option<BTreeMap<String, BTreeMap<String, String>>>, + pub exclude: Option<Vec<String>>, + pub include: Option<Vec<String>>, + pub rust_version: Option<RustVersion>, + // We use skip here since it will never be present when deserializing + // and we don't want it present when serializing + #[serde(skip)] + pub ws_root: PathBuf, +} + +/// Represents the `package`/`project` sections of a `Cargo.toml`. +/// +/// Note that the order of the fields matters, since this is the order they +/// are serialized to a TOML file. For example, you cannot have values after +/// the field `metadata`, since it is a table and values cannot appear after +/// tables. +#[derive(Deserialize, Serialize, Clone, Debug)] +#[serde(rename_all = "kebab-case")] +pub struct TomlPackage { + pub edition: Option<MaybeWorkspaceString>, + pub rust_version: Option<MaybeWorkspaceRustVersion>, + pub name: String, + pub version: Option<MaybeWorkspaceSemverVersion>, + pub authors: Option<MaybeWorkspaceVecString>, + pub build: Option<StringOrBool>, + pub metabuild: Option<StringOrVec>, + pub default_target: Option<String>, + pub forced_target: Option<String>, + pub links: Option<String>, + pub exclude: Option<MaybeWorkspaceVecString>, + pub include: Option<MaybeWorkspaceVecString>, + pub publish: Option<MaybeWorkspaceVecStringOrBool>, + pub workspace: Option<String>, + pub im_a_teapot: Option<bool>, + pub autobins: Option<bool>, + pub autoexamples: Option<bool>, + pub autotests: Option<bool>, + pub autobenches: Option<bool>, + pub default_run: Option<String>, + + // Package metadata. + pub description: Option<MaybeWorkspaceString>, + pub homepage: Option<MaybeWorkspaceString>, + pub documentation: Option<MaybeWorkspaceString>, + pub readme: Option<MaybeWorkspaceStringOrBool>, + pub keywords: Option<MaybeWorkspaceVecString>, + pub categories: Option<MaybeWorkspaceVecString>, + pub license: Option<MaybeWorkspaceString>, + pub license_file: Option<MaybeWorkspaceString>, + pub repository: Option<MaybeWorkspaceString>, + pub resolver: Option<String>, + + pub metadata: Option<toml::Value>, + + /// Provide a helpful error message for a common user error. + #[serde(rename = "cargo-features", skip_serializing)] + pub _invalid_cargo_features: Option<InvalidCargoFeatures>, +} + +/// An enum that allows for inheriting keys from a workspace in a Cargo.toml. +#[derive(Serialize, Copy, Clone, Debug)] +#[serde(untagged)] +pub enum MaybeWorkspace<T, W> { + /// The "defined" type, or the type that that is used when not inheriting from a workspace. + Defined(T), + /// The type when inheriting from a workspace. + Workspace(W), +} + +//. This already has a `Deserialize` impl from version_trim_whitespace +pub type MaybeWorkspaceSemverVersion = MaybeWorkspace<semver::Version, TomlWorkspaceField>; +impl<'de> de::Deserialize<'de> for MaybeWorkspaceSemverVersion { + fn deserialize<D>(d: D) -> Result<Self, D::Error> + where + D: de::Deserializer<'de>, + { + UntaggedEnumVisitor::new() + .expecting("SemVer version") + .string( + |value| match value.trim().parse().map_err(de::Error::custom) { + Ok(parsed) => Ok(MaybeWorkspace::Defined(parsed)), + Err(e) => Err(e), + }, + ) + .map(|value| value.deserialize().map(MaybeWorkspace::Workspace)) + .deserialize(d) + } +} + +pub type MaybeWorkspaceString = MaybeWorkspace<String, TomlWorkspaceField>; +impl<'de> de::Deserialize<'de> for MaybeWorkspaceString { + fn deserialize<D>(d: D) -> Result<Self, D::Error> + 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<E>(self, value: String) -> Result<Self::Value, E> + where + E: de::Error, + { + Ok(MaybeWorkspaceString::Defined(value)) + } + + fn visit_map<V>(self, map: V) -> Result<Self::Value, V::Error> + where + V: de::MapAccess<'de>, + { + let mvd = de::value::MapAccessDeserializer::new(map); + TomlWorkspaceField::deserialize(mvd).map(MaybeWorkspace::Workspace) + } + } + + d.deserialize_any(Visitor) + } +} + +pub type MaybeWorkspaceRustVersion = MaybeWorkspace<RustVersion, TomlWorkspaceField>; +impl<'de> de::Deserialize<'de> for MaybeWorkspaceRustVersion { + fn deserialize<D>(d: D) -> Result<Self, D::Error> + where + D: de::Deserializer<'de>, + { + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor { + type Value = MaybeWorkspaceRustVersion; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + f.write_str("a semver or workspace") + } + + fn visit_string<E>(self, value: String) -> Result<Self::Value, E> + where + E: de::Error, + { + let value = value.parse::<RustVersion>().map_err(|e| E::custom(e))?; + Ok(MaybeWorkspaceRustVersion::Defined(value)) + } + + fn visit_map<V>(self, map: V) -> Result<Self::Value, V::Error> + where + V: de::MapAccess<'de>, + { + let mvd = de::value::MapAccessDeserializer::new(map); + TomlWorkspaceField::deserialize(mvd).map(MaybeWorkspace::Workspace) + } + } + + d.deserialize_any(Visitor) + } +} + +pub type MaybeWorkspaceVecString = MaybeWorkspace<Vec<String>, TomlWorkspaceField>; +impl<'de> de::Deserialize<'de> for MaybeWorkspaceVecString { + fn deserialize<D>(d: D) -> Result<Self, D::Error> + 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<A>(self, v: A) -> Result<Self::Value, A::Error> + where + A: de::SeqAccess<'de>, + { + let seq = de::value::SeqAccessDeserializer::new(v); + Vec::deserialize(seq).map(MaybeWorkspace::Defined) + } + + fn visit_map<V>(self, map: V) -> Result<Self::Value, V::Error> + where + V: de::MapAccess<'de>, + { + let mvd = de::value::MapAccessDeserializer::new(map); + TomlWorkspaceField::deserialize(mvd).map(MaybeWorkspace::Workspace) + } + } + + d.deserialize_any(Visitor) + } +} + +pub type MaybeWorkspaceStringOrBool = MaybeWorkspace<StringOrBool, TomlWorkspaceField>; +impl<'de> de::Deserialize<'de> for MaybeWorkspaceStringOrBool { + fn deserialize<D>(d: D) -> Result<Self, D::Error> + 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<E>(self, v: bool) -> Result<Self::Value, E> + where + E: de::Error, + { + let b = de::value::BoolDeserializer::new(v); + StringOrBool::deserialize(b).map(MaybeWorkspace::Defined) + } + + fn visit_string<E>(self, v: String) -> Result<Self::Value, E> + where + E: de::Error, + { + let string = de::value::StringDeserializer::new(v); + StringOrBool::deserialize(string).map(MaybeWorkspace::Defined) + } + + fn visit_map<V>(self, map: V) -> Result<Self::Value, V::Error> + where + V: de::MapAccess<'de>, + { + let mvd = de::value::MapAccessDeserializer::new(map); + TomlWorkspaceField::deserialize(mvd).map(MaybeWorkspace::Workspace) + } + } + + d.deserialize_any(Visitor) + } +} + +pub type MaybeWorkspaceVecStringOrBool = MaybeWorkspace<VecStringOrBool, TomlWorkspaceField>; +impl<'de> de::Deserialize<'de> for MaybeWorkspaceVecStringOrBool { + fn deserialize<D>(d: D) -> Result<Self, D::Error> + 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<E>(self, v: bool) -> Result<Self::Value, E> + where + E: de::Error, + { + let b = de::value::BoolDeserializer::new(v); + VecStringOrBool::deserialize(b).map(MaybeWorkspace::Defined) + } + + fn visit_seq<A>(self, v: A) -> Result<Self::Value, A::Error> + where + A: de::SeqAccess<'de>, + { + let seq = de::value::SeqAccessDeserializer::new(v); + VecStringOrBool::deserialize(seq).map(MaybeWorkspace::Defined) + } + + fn visit_map<V>(self, map: V) -> Result<Self::Value, V::Error> + where + V: de::MapAccess<'de>, + { + let mvd = de::value::MapAccessDeserializer::new(map); + TomlWorkspaceField::deserialize(mvd).map(MaybeWorkspace::Workspace) + } + } + + d.deserialize_any(Visitor) + } +} + +pub type MaybeWorkspaceBtreeMap = + MaybeWorkspace<BTreeMap<String, BTreeMap<String, String>>, TomlWorkspaceField>; + +impl<'de> de::Deserialize<'de> for MaybeWorkspaceBtreeMap { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: de::Deserializer<'de>, + { + let value = serde_value::Value::deserialize(deserializer)?; + + if let Ok(w) = TomlWorkspaceField::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")) + }; + } + BTreeMap::deserialize(serde_value::ValueDeserializer::<D::Error>::new(value)) + .map(MaybeWorkspace::Defined) + } +} + +#[derive(Deserialize, Serialize, Copy, Clone, Debug)] +#[serde(rename_all = "kebab-case")] +pub struct TomlWorkspaceField { + #[serde(deserialize_with = "bool_no_false")] + pub workspace: bool, +} + +fn bool_no_false<'de, D: de::Deserializer<'de>>(deserializer: D) -> Result<bool, D::Error> { + let b: bool = Deserialize::deserialize(deserializer)?; + if b { + Ok(b) + } else { + Err(de::Error::custom("`workspace` cannot be false")) + } +} + +pub type MaybeWorkspaceDependency = MaybeWorkspace<TomlDependency, TomlWorkspaceDependency>; + +impl MaybeWorkspaceDependency { + pub fn unused_keys(&self) -> Vec<String> { + match self { + MaybeWorkspaceDependency::Defined(d) => d.unused_keys(), + MaybeWorkspaceDependency::Workspace(w) => w.unused_keys.keys().cloned().collect(), + } + } +} + +impl<'de> de::Deserialize<'de> for MaybeWorkspaceDependency { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + 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::<D::Error>::new(value)) + .map(MaybeWorkspace::Defined) + } +} + +#[derive(Deserialize, Serialize, Clone, Debug)] +#[serde(rename_all = "kebab-case")] +pub struct TomlWorkspaceDependency { + pub workspace: bool, + pub features: Option<Vec<String>>, + pub default_features: Option<bool>, + #[serde(rename = "default_features")] + pub default_features2: Option<bool>, + pub optional: Option<bool>, + pub public: Option<bool>, + + /// This is here to provide a way to see the "unused manifest keys" when deserializing + #[serde(skip_serializing)] + #[serde(flatten)] + pub unused_keys: BTreeMap<String, toml::Value>, +} + +impl TomlWorkspaceDependency { + pub fn default_features(&self) -> Option<bool> { + self.default_features.or(self.default_features2) + } +} + +#[derive(Clone, Debug, Serialize)] +#[serde(untagged)] +pub enum TomlDependency<P: Clone = String> { + /// In the simple format, only a version is specified, eg. + /// `package = "<version>"` + Simple(String), + /// The simple format is equivalent to a detailed dependency + /// specifying only a version, eg. + /// `package = { version = "<version>" }` + Detailed(DetailedTomlDependency<P>), +} + +impl TomlDependency { + pub fn is_version_specified(&self) -> bool { + match self { + TomlDependency::Detailed(d) => d.version.is_some(), + TomlDependency::Simple(..) => true, + } + } + + pub fn is_optional(&self) -> bool { + match self { + TomlDependency::Detailed(d) => d.optional.unwrap_or(false), + TomlDependency::Simple(..) => false, + } + } + + pub fn unused_keys(&self) -> Vec<String> { + match self { + TomlDependency::Simple(_) => vec![], + TomlDependency::Detailed(detailed) => detailed.unused_keys.keys().cloned().collect(), + } + } +} + +impl<'de, P: Deserialize<'de> + Clone> de::Deserialize<'de> for TomlDependency<P> { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: de::Deserializer<'de>, + { + UntaggedEnumVisitor::new() + .expecting( + "a version string like \"0.9.8\" or a \ + detailed dependency like { version = \"0.9.8\" }", + ) + .string(|value| Ok(TomlDependency::Simple(value.to_owned()))) + .map(|value| value.deserialize().map(TomlDependency::Detailed)) + .deserialize(deserializer) + } +} + +#[derive(Deserialize, Serialize, Clone, Debug)] +#[serde(rename_all = "kebab-case")] +pub struct DetailedTomlDependency<P: Clone = String> { + pub version: Option<String>, + pub registry: Option<String>, + /// The URL of the `registry` field. + /// This is an internal implementation detail. When Cargo creates a + /// package, it replaces `registry` with `registry-index` so that the + /// manifest contains the correct URL. All users won't have the same + /// registry names configured, so Cargo can't rely on just the name for + /// crates published by other users. + pub registry_index: Option<String>, + // `path` is relative to the file it appears in. If that's a `Cargo.toml`, it'll be relative to + // that TOML file, and if it's a `.cargo/config` file, it'll be relative to that file. + pub path: Option<P>, + pub git: Option<String>, + pub branch: Option<String>, + pub tag: Option<String>, + pub rev: Option<String>, + pub features: Option<Vec<String>>, + pub optional: Option<bool>, + pub default_features: Option<bool>, + #[serde(rename = "default_features")] + pub default_features2: Option<bool>, + pub package: Option<String>, + pub public: Option<bool>, + + /// One or more of `bin`, `cdylib`, `staticlib`, `bin:<name>`. + pub artifact: Option<StringOrVec>, + /// If set, the artifact should also be a dependency + pub lib: Option<bool>, + /// A platform name, like `x86_64-apple-darwin` + pub target: Option<String>, + + /// This is here to provide a way to see the "unused manifest keys" when deserializing + #[serde(skip_serializing)] + #[serde(flatten)] + pub unused_keys: BTreeMap<String, toml::Value>, +} + +impl<P: Clone> DetailedTomlDependency<P> { + pub fn default_features(&self) -> Option<bool> { + self.default_features.or(self.default_features2) + } +} + +// Explicit implementation so we avoid pulling in P: Default +impl<P: Clone> Default for DetailedTomlDependency<P> { + fn default() -> Self { + Self { + version: Default::default(), + registry: Default::default(), + registry_index: Default::default(), + path: Default::default(), + git: Default::default(), + branch: Default::default(), + tag: Default::default(), + rev: Default::default(), + features: Default::default(), + optional: Default::default(), + default_features: Default::default(), + default_features2: Default::default(), + package: Default::default(), + public: Default::default(), + artifact: Default::default(), + lib: Default::default(), + target: Default::default(), + unused_keys: Default::default(), + } + } +} + +#[derive(Deserialize, Serialize, Clone, Debug, Default)] +pub struct TomlProfiles(pub BTreeMap<String, TomlProfile>); + +impl TomlProfiles { + pub fn get_all(&self) -> &BTreeMap<String, TomlProfile> { + &self.0 + } + + pub fn get(&self, name: &str) -> Option<&TomlProfile> { + self.0.get(name) + } +} + +#[derive(Deserialize, Serialize, Clone, Debug, Default, Eq, PartialEq)] +#[serde(default, rename_all = "kebab-case")] +pub struct TomlProfile { + pub opt_level: Option<TomlOptLevel>, + pub lto: Option<StringOrBool>, + pub codegen_backend: Option<String>, + pub codegen_units: Option<u32>, + pub debug: Option<TomlDebugInfo>, + pub split_debuginfo: Option<String>, + pub debug_assertions: Option<bool>, + pub rpath: Option<bool>, + pub panic: Option<String>, + pub overflow_checks: Option<bool>, + pub incremental: Option<bool>, + pub dir_name: Option<String>, + pub inherits: Option<String>, + pub strip: Option<StringOrBool>, + // Note that `rustflags` is used for the cargo-feature `profile_rustflags` + pub rustflags: Option<Vec<String>>, + // These two fields must be last because they are sub-tables, and TOML + // requires all non-tables to be listed first. + pub package: Option<BTreeMap<ProfilePackageSpec, TomlProfile>>, + pub build_override: Option<Box<TomlProfile>>, + /// Unstable feature `-Ztrim-paths`. + pub trim_paths: Option<TomlTrimPaths>, +} + +#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] +pub enum ProfilePackageSpec { + Spec(PackageIdSpec), + All, +} + +impl fmt::Display for ProfilePackageSpec { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ProfilePackageSpec::Spec(spec) => spec.fmt(f), + ProfilePackageSpec::All => f.write_str("*"), + } + } +} + +impl ser::Serialize for ProfilePackageSpec { + fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error> + where + S: ser::Serializer, + { + self.to_string().serialize(s) + } +} + +impl<'de> de::Deserialize<'de> for ProfilePackageSpec { + fn deserialize<D>(d: D) -> Result<ProfilePackageSpec, D::Error> + where + D: de::Deserializer<'de>, + { + let string = String::deserialize(d)?; + if string == "*" { + Ok(ProfilePackageSpec::All) + } else { + PackageIdSpec::parse(&string) + .map_err(de::Error::custom) + .map(ProfilePackageSpec::Spec) + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TomlOptLevel(pub String); + +impl ser::Serialize for TomlOptLevel { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: ser::Serializer, + { + match self.0.parse::<u32>() { + Ok(n) => n.serialize(serializer), + Err(_) => self.0.serialize(serializer), + } + } +} + +impl<'de> de::Deserialize<'de> for TomlOptLevel { + fn deserialize<D>(d: D) -> Result<TomlOptLevel, D::Error> + where + D: de::Deserializer<'de>, + { + use serde::de::Error as _; + UntaggedEnumVisitor::new() + .expecting("an optimization level") + .i64(|value| Ok(TomlOptLevel(value.to_string()))) + .string(|value| { + if value == "s" || value == "z" { + Ok(TomlOptLevel(value.to_string())) + } else { + Err(serde_untagged::de::Error::custom(format!( + "must be `0`, `1`, `2`, `3`, `s` or `z`, \ + but found the string: \"{}\"", + value + ))) + } + }) + .deserialize(d) + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)] +pub enum TomlDebugInfo { + None, + LineDirectivesOnly, + LineTablesOnly, + Limited, + Full, +} + +impl Display for TomlDebugInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TomlDebugInfo::None => f.write_char('0'), + TomlDebugInfo::Limited => f.write_char('1'), + TomlDebugInfo::Full => f.write_char('2'), + TomlDebugInfo::LineDirectivesOnly => f.write_str("line-directives-only"), + TomlDebugInfo::LineTablesOnly => f.write_str("line-tables-only"), + } + } +} + +impl ser::Serialize for TomlDebugInfo { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: ser::Serializer, + { + match self { + Self::None => 0.serialize(serializer), + Self::LineDirectivesOnly => "line-directives-only".serialize(serializer), + Self::LineTablesOnly => "line-tables-only".serialize(serializer), + Self::Limited => 1.serialize(serializer), + Self::Full => 2.serialize(serializer), + } + } +} + +impl<'de> de::Deserialize<'de> for TomlDebugInfo { + fn deserialize<D>(d: D) -> Result<TomlDebugInfo, D::Error> + where + D: de::Deserializer<'de>, + { + use serde::de::Error as _; + let expecting = "a boolean, 0, 1, 2, \"line-tables-only\", or \"line-directives-only\""; + UntaggedEnumVisitor::new() + .expecting(expecting) + .bool(|value| { + Ok(if value { + TomlDebugInfo::Full + } else { + TomlDebugInfo::None + }) + }) + .i64(|value| { + let debuginfo = match value { + 0 => TomlDebugInfo::None, + 1 => TomlDebugInfo::Limited, + 2 => TomlDebugInfo::Full, + _ => { + return Err(serde_untagged::de::Error::invalid_value( + Unexpected::Signed(value), + &expecting, + )) + } + }; + Ok(debuginfo) + }) + .string(|value| { + let debuginfo = match value { + "none" => TomlDebugInfo::None, + "limited" => TomlDebugInfo::Limited, + "full" => TomlDebugInfo::Full, + "line-directives-only" => TomlDebugInfo::LineDirectivesOnly, + "line-tables-only" => TomlDebugInfo::LineTablesOnly, + _ => { + return Err(serde_untagged::de::Error::invalid_value( + Unexpected::Str(value), + &expecting, + )) + } + }; + Ok(debuginfo) + }) + .deserialize(d) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize)] +#[serde(untagged, rename_all = "kebab-case")] +pub enum TomlTrimPaths { + Values(Vec<TomlTrimPathsValue>), + All, +} + +impl TomlTrimPaths { + pub fn none() -> Self { + TomlTrimPaths::Values(Vec::new()) + } + + pub fn is_none(&self) -> bool { + match self { + TomlTrimPaths::Values(v) => v.is_empty(), + TomlTrimPaths::All => false, + } + } +} + +impl<'de> de::Deserialize<'de> for TomlTrimPaths { + fn deserialize<D>(d: D) -> Result<TomlTrimPaths, D::Error> + where + D: de::Deserializer<'de>, + { + use serde::de::Error as _; + let expecting = r#"a boolean, "none", "diagnostics", "macro", "object", "all", or an array with these options"#; + UntaggedEnumVisitor::new() + .expecting(expecting) + .bool(|value| { + Ok(if value { + TomlTrimPaths::All + } else { + TomlTrimPaths::none() + }) + }) + .string(|v| match v { + "none" => Ok(TomlTrimPaths::none()), + "all" => Ok(TomlTrimPaths::All), + v => { + let d = v.into_deserializer(); + let err = |_: D::Error| { + serde_untagged::de::Error::custom(format!("expected {expecting}")) + }; + TomlTrimPathsValue::deserialize(d) + .map_err(err) + .map(|v| v.into()) + } + }) + .seq(|seq| { + let seq: Vec<String> = seq.deserialize()?; + let seq: Vec<_> = seq + .into_iter() + .map(|s| TomlTrimPathsValue::deserialize(s.into_deserializer())) + .collect::<Result<_, _>>()?; + Ok(seq.into()) + }) + .deserialize(d) + } +} + +impl fmt::Display for TomlTrimPaths { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TomlTrimPaths::All => write!(f, "all"), + TomlTrimPaths::Values(v) if v.is_empty() => write!(f, "none"), + TomlTrimPaths::Values(v) => { + let mut iter = v.iter(); + if let Some(value) = iter.next() { + write!(f, "{value}")?; + } + for value in iter { + write!(f, ",{value}")?; + } + Ok(()) + } + } + } +} + +impl From<TomlTrimPathsValue> for TomlTrimPaths { + fn from(value: TomlTrimPathsValue) -> Self { + TomlTrimPaths::Values(vec![value]) + } +} + +impl From<Vec<TomlTrimPathsValue>> for TomlTrimPaths { + fn from(value: Vec<TomlTrimPathsValue>) -> Self { + TomlTrimPaths::Values(value) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum TomlTrimPathsValue { + Diagnostics, + Macro, + Object, +} + +impl TomlTrimPathsValue { + pub fn as_str(&self) -> &'static str { + match self { + TomlTrimPathsValue::Diagnostics => "diagnostics", + TomlTrimPathsValue::Macro => "macro", + TomlTrimPathsValue::Object => "object", + } + } +} + +impl fmt::Display for TomlTrimPathsValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} + +pub type TomlLibTarget = TomlTarget; +pub type TomlBinTarget = TomlTarget; +pub type TomlExampleTarget = TomlTarget; +pub type TomlTestTarget = TomlTarget; +pub type TomlBenchTarget = TomlTarget; + +#[derive(Default, Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "kebab-case")] +pub struct TomlTarget { + pub name: Option<String>, + + // The intention was to only accept `crate-type` here but historical + // versions of Cargo also accepted `crate_type`, so look for both. + pub crate_type: Option<Vec<String>>, + #[serde(rename = "crate_type")] + pub crate_type2: Option<Vec<String>>, + + pub path: Option<PathValue>, + // Note that `filename` is used for the cargo-feature `different_binary_name` + pub filename: Option<String>, + pub test: Option<bool>, + pub doctest: Option<bool>, + pub bench: Option<bool>, + pub doc: Option<bool>, + pub plugin: Option<bool>, + pub doc_scrape_examples: Option<bool>, + #[serde(rename = "proc-macro")] + pub proc_macro_raw: Option<bool>, + #[serde(rename = "proc_macro")] + pub proc_macro_raw2: Option<bool>, + pub harness: Option<bool>, + pub required_features: Option<Vec<String>>, + pub edition: Option<String>, +} + +impl TomlTarget { + pub fn new() -> TomlTarget { + TomlTarget::default() + } + + pub fn proc_macro(&self) -> Option<bool> { + self.proc_macro_raw.or(self.proc_macro_raw2).or_else(|| { + if let Some(types) = self.crate_types() { + if types.contains(&"proc-macro".to_string()) { + return Some(true); + } + } + None + }) + } + + pub fn crate_types(&self) -> Option<&Vec<String>> { + self.crate_type + .as_ref() + .or_else(|| self.crate_type2.as_ref()) + } +} + +/// Corresponds to a `target` entry, but `TomlTarget` is already used. +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "kebab-case")] +pub struct TomlPlatform { + pub dependencies: Option<BTreeMap<String, MaybeWorkspaceDependency>>, + pub build_dependencies: Option<BTreeMap<String, MaybeWorkspaceDependency>>, + #[serde(rename = "build_dependencies")] + pub build_dependencies2: Option<BTreeMap<String, MaybeWorkspaceDependency>>, + pub dev_dependencies: Option<BTreeMap<String, MaybeWorkspaceDependency>>, + #[serde(rename = "dev_dependencies")] + pub dev_dependencies2: Option<BTreeMap<String, MaybeWorkspaceDependency>>, +} + +impl TomlPlatform { + pub fn dev_dependencies(&self) -> Option<&BTreeMap<String, MaybeWorkspaceDependency>> { + self.dev_dependencies + .as_ref() + .or(self.dev_dependencies2.as_ref()) + } + + pub fn build_dependencies(&self) -> Option<&BTreeMap<String, MaybeWorkspaceDependency>> { + self.build_dependencies + .as_ref() + .or(self.build_dependencies2.as_ref()) + } +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +#[serde(expecting = "a lints table")] +#[serde(rename_all = "kebab-case")] +pub struct MaybeWorkspaceLints { + #[serde(skip_serializing_if = "is_false")] + #[serde(deserialize_with = "bool_no_false", default)] + pub workspace: bool, + #[serde(flatten)] + pub lints: TomlLints, +} + +fn is_false(b: &bool) -> bool { + !b +} + +pub type TomlLints = BTreeMap<String, TomlToolLints>; + +pub type TomlToolLints = BTreeMap<String, TomlLint>; + +#[derive(Serialize, Debug, Clone)] +#[serde(untagged)] +pub enum TomlLint { + Level(TomlLintLevel), + Config(TomlLintConfig), +} + +impl<'de> Deserialize<'de> for TomlLint { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: de::Deserializer<'de>, + { + UntaggedEnumVisitor::new() + .string(|string| { + TomlLintLevel::deserialize(string.into_deserializer()).map(TomlLint::Level) + }) + .map(|map| map.deserialize().map(TomlLint::Config)) + .deserialize(deserializer) + } +} + +impl TomlLint { + pub fn level(&self) -> TomlLintLevel { + match self { + Self::Level(level) => *level, + Self::Config(config) => config.level, + } + } + + pub fn priority(&self) -> i8 { + match self { + Self::Level(_) => 0, + Self::Config(config) => config.priority, + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "kebab-case")] +pub struct TomlLintConfig { + pub level: TomlLintLevel, + #[serde(default)] + pub priority: i8, +} + +#[derive(Serialize, Deserialize, Debug, Copy, Clone)] +#[serde(rename_all = "kebab-case")] +pub enum TomlLintLevel { + Forbid, + Deny, + Warn, + Allow, +} + +#[derive(Copy, Clone, Debug)] +pub struct InvalidCargoFeatures {} + +impl<'de> de::Deserialize<'de> for InvalidCargoFeatures { + fn deserialize<D>(_d: D) -> Result<Self, D::Error> + where + D: de::Deserializer<'de>, + { + use serde::de::Error as _; + + Err(D::Error::custom( + "the field `cargo-features` should be set at the top of Cargo.toml before any tables", + )) + } +} + +/// A StringOrVec can be parsed from either a TOML string or array, +/// but is always stored as a vector. +#[derive(Clone, Debug, Serialize, Eq, PartialEq, PartialOrd, Ord)] +pub struct StringOrVec(pub Vec<String>); + +impl StringOrVec { + pub fn iter<'a>(&'a self) -> std::slice::Iter<'a, String> { + self.0.iter() + } +} + +impl<'de> de::Deserialize<'de> for StringOrVec { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: de::Deserializer<'de>, + { + UntaggedEnumVisitor::new() + .expecting("string or list of strings") + .string(|value| Ok(StringOrVec(vec![value.to_owned()]))) + .seq(|value| value.deserialize().map(StringOrVec)) + .deserialize(deserializer) + } +} + +#[derive(Clone, Debug, Serialize, Eq, PartialEq)] +#[serde(untagged)] +pub enum StringOrBool { + String(String), + Bool(bool), +} + +impl<'de> Deserialize<'de> for StringOrBool { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: de::Deserializer<'de>, + { + UntaggedEnumVisitor::new() + .bool(|b| Ok(StringOrBool::Bool(b))) + .string(|s| Ok(StringOrBool::String(s.to_owned()))) + .deserialize(deserializer) + } +} + +#[derive(PartialEq, Clone, Debug, Serialize)] +#[serde(untagged)] +pub enum VecStringOrBool { + VecString(Vec<String>), + Bool(bool), +} + +impl<'de> de::Deserialize<'de> for VecStringOrBool { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: de::Deserializer<'de>, + { + UntaggedEnumVisitor::new() + .expecting("a boolean or vector of strings") + .bool(|value| Ok(VecStringOrBool::Bool(value))) + .seq(|value| value.deserialize().map(VecStringOrBool::VecString)) + .deserialize(deserializer) + } +} + +#[derive(Clone)] +pub struct PathValue(pub PathBuf); + +impl fmt::Debug for PathValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl ser::Serialize for PathValue { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: ser::Serializer, + { + self.0.serialize(serializer) + } +} + +impl<'de> de::Deserialize<'de> for PathValue { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: de::Deserializer<'de>, + { + Ok(PathValue(String::deserialize(deserializer)?.into())) + } +} diff --git a/src/tools/cargo/src/cargo/util/toml/targets.rs b/src/tools/cargo/src/cargo/util/toml/targets.rs index afc4f258f..9d456ffd7 100644 --- a/src/tools/cargo/src/cargo/util/toml/targets.rs +++ b/src/tools/cargo/src/cargo/util/toml/targets.rs @@ -14,7 +14,7 @@ use std::collections::HashSet; use std::fs::{self, DirEntry}; use std::path::{Path, PathBuf}; -use super::{ +use super::schema::{ PathValue, StringOrBool, StringOrVec, TomlBenchTarget, TomlBinTarget, TomlExampleTarget, TomlLibTarget, TomlManifest, TomlTarget, TomlTestTarget, }; @@ -23,6 +23,7 @@ use crate::core::compiler::CrateType; use crate::core::{Edition, Feature, Features, Target}; use crate::util::errors::CargoResult; use crate::util::restricted_names; +use crate::util::toml::warn_on_deprecated; use anyhow::Context as _; @@ -31,7 +32,7 @@ const DEFAULT_BENCH_DIR_NAME: &'static str = "benches"; const DEFAULT_EXAMPLE_DIR_NAME: &'static str = "examples"; const DEFAULT_BIN_DIR_NAME: &'static str = "bin"; -pub fn targets( +pub(super) fn targets( features: &Features, manifest: &TomlManifest, package_name: &str, @@ -105,7 +106,7 @@ pub fn targets( )?); // processing the custom build script - if let Some(custom_build) = manifest.maybe_custom_build(custom_build, package_root) { + if let Some(custom_build) = maybe_custom_build(custom_build, package_root) { if metabuild.is_some() { anyhow::bail!("cannot specify both `metabuild` and `build`"); } @@ -172,8 +173,8 @@ fn clean_lib( }; let Some(ref lib) = lib else { return Ok(None) }; - lib.validate_proc_macro(warnings); - lib.validate_crate_types("library", warnings); + validate_proc_macro(lib, "library", warnings); + validate_crate_types(lib, "library", warnings); validate_target_name(lib, "library", "lib", warnings)?; @@ -181,20 +182,22 @@ fn clean_lib( (Some(path), _) => package_root.join(&path.0), (None, Some(path)) => path, (None, None) => { - let legacy_path = package_root.join("src").join(format!("{}.rs", lib.name())); + let legacy_path = package_root + .join("src") + .join(format!("{}.rs", name_or_panic(lib))); if edition == Edition::Edition2015 && legacy_path.exists() { warnings.push(format!( "path `{}` was erroneously implicitly accepted for library `{}`,\n\ please rename the file to `src/lib.rs` or set lib.path in Cargo.toml", legacy_path.display(), - lib.name() + name_or_panic(lib) )); legacy_path } else { anyhow::bail!( "can't find library `{}`, \ rename file to `src/lib.rs` or specify lib.path", - lib.name() + name_or_panic(lib) ) } } @@ -216,7 +219,7 @@ fn clean_lib( { anyhow::bail!(format!( "library `{}` cannot set the crate type of both `dylib` and `cdylib`", - lib.name() + name_or_panic(lib) )); } (Some(kinds), _, _) if kinds.contains(&"proc-macro".to_string()) => { @@ -224,12 +227,12 @@ fn clean_lib( // This is a warning to retain backwards compatibility. warnings.push(format!( "proc-macro library `{}` should not specify `plugin = true`", - lib.name() + name_or_panic(lib) )); } warnings.push(format!( "library `{}` should only specify `proc-macro = true` instead of setting `crate-type`", - lib.name() + name_or_panic(lib) )); if kinds.len() > 1 { anyhow::bail!("cannot mix `proc-macro` crate type with others"); @@ -245,7 +248,7 @@ fn clean_lib( (None, _, _) => vec![CrateType::Lib], }; - let mut target = Target::lib_target(&lib.name(), crate_types, path, edition); + let mut target = Target::lib_target(name_or_panic(lib), crate_types, path, edition); configure(lib, &mut target)?; Ok(Some(target)) } @@ -285,7 +288,7 @@ fn clean_bins( validate_target_name(bin, "binary", "bin", warnings)?; - let name = bin.name(); + let name = name_or_panic(bin).to_owned(); if let Some(crate_types) = bin.crate_types() { if !crate_types.is_empty() { @@ -309,7 +312,7 @@ fn clean_bins( if restricted_names::is_conflicting_artifact_name(&name) { anyhow::bail!( "the binary target name `{}` is forbidden, \ - it conflicts with with cargo's build directory names", + it conflicts with cargo's build directory names", name ) } @@ -320,12 +323,12 @@ fn clean_bins( let mut result = Vec::new(); for bin in &bins { let path = target_path(bin, &inferred, "bin", package_root, edition, &mut |_| { - if let Some(legacy_path) = legacy_bin_path(package_root, &bin.name(), has_lib) { + if let Some(legacy_path) = legacy_bin_path(package_root, name_or_panic(bin), has_lib) { warnings.push(format!( "path `{}` was erroneously implicitly accepted for binary `{}`,\n\ please set bin.path in Cargo.toml", legacy_path.display(), - bin.name() + name_or_panic(bin) )); Some(legacy_path) } else { @@ -338,7 +341,7 @@ fn clean_bins( }; let mut target = Target::bin_target( - &bin.name(), + name_or_panic(bin), bin.filename.clone(), path, bin.required_features.clone(), @@ -398,14 +401,14 @@ fn clean_examples( let mut result = Vec::new(); for (path, toml) in targets { - toml.validate_crate_types("example", warnings); + validate_crate_types(&toml, "example", warnings); let crate_types = match toml.crate_types() { Some(kinds) => kinds.iter().map(|s| s.into()).collect(), None => Vec::new(), }; let mut target = Target::example_target( - &toml.name(), + name_or_panic(&toml), crate_types, path, toml.required_features.clone(), @@ -443,8 +446,12 @@ fn clean_tests( let mut result = Vec::new(); for (path, toml) in targets { - let mut target = - Target::test_target(&toml.name(), path, toml.required_features.clone(), edition); + let mut target = Target::test_target( + name_or_panic(&toml), + path, + toml.required_features.clone(), + edition, + ); configure(&toml, &mut target)?; result.push(target); } @@ -464,14 +471,14 @@ fn clean_benches( let targets = { let mut legacy_bench_path = |bench: &TomlTarget| { let legacy_path = package_root.join("src").join("bench.rs"); - if !(bench.name() == "bench" && legacy_path.exists()) { + if !(name_or_panic(bench) == "bench" && legacy_path.exists()) { return None; } legacy_warnings.push(format!( "path `{}` was erroneously implicitly accepted for benchmark `{}`,\n\ please set bench.path in Cargo.toml", legacy_path.display(), - bench.name() + name_or_panic(bench) )); Some(legacy_path) }; @@ -497,8 +504,12 @@ fn clean_benches( let mut result = Vec::new(); for (path, toml) in targets { - let mut target = - Target::bench_target(&toml.name(), path, toml.required_features.clone(), edition); + let mut target = Target::bench_target( + name_or_panic(&toml), + path, + toml.required_features.clone(), + edition, + ); configure(&toml, &mut target)?; result.push(target); } @@ -784,8 +795,8 @@ fn validate_target_name( /// Will check a list of toml targets, and make sure the target names are unique within a vector. fn validate_unique_names(targets: &[TomlTarget], target_kind: &str) -> CargoResult<()> { let mut seen = HashSet::new(); - for name in targets.iter().map(|e| e.name()) { - if !seen.insert(name.clone()) { + for name in targets.iter().map(|e| name_or_panic(e)) { + if !seen.insert(name) { anyhow::bail!( "found duplicate {target_kind} name {name}, \ but all {target_kind} targets must have a unique name", @@ -875,7 +886,7 @@ fn target_path_not_found_error_message( return [target_path_file, target_path_subdir]; } - let target_name = target.name(); + let target_name = name_or_panic(target); let commonly_wrong_paths = possible_target_paths(&target_name, target_kind, true); let possible_paths = possible_target_paths(&target_name, target_kind, false); let existing_wrong_path_index = match ( @@ -922,7 +933,7 @@ fn target_path( // Should we verify that this path exists here? return Ok(package_root.join(&path.0)); } - let name = target.name(); + let name = name_or_panic(target).to_owned(); let mut matching = inferred .iter() @@ -955,7 +966,7 @@ fn target_path( "\ cannot infer path for `{}` {} Cargo doesn't know which to use because multiple target files found at `{}` and `{}`.", - target.name(), + name_or_panic(target), target_kind, p0.strip_prefix(package_root).unwrap_or(&p0).display(), p1.strip_prefix(package_root).unwrap_or(&p1).display(), @@ -964,3 +975,52 @@ Cargo doesn't know which to use because multiple target files found at `{}` and (None, Some(_)) => unreachable!(), } } + +/// Returns the path to the build script if one exists for this crate. +fn maybe_custom_build(build: &Option<StringOrBool>, package_root: &Path) -> Option<PathBuf> { + let build_rs = package_root.join("build.rs"); + match *build { + // Explicitly no build script. + Some(StringOrBool::Bool(false)) => None, + Some(StringOrBool::Bool(true)) => Some(build_rs), + Some(StringOrBool::String(ref s)) => Some(PathBuf::from(s)), + None => { + // If there is a `build.rs` file next to the `Cargo.toml`, assume it is + // a build script. + if build_rs.is_file() { + Some(build_rs) + } else { + None + } + } + } +} + +fn name_or_panic(target: &TomlTarget) -> &str { + target + .name + .as_deref() + .unwrap_or_else(|| panic!("target name is required")) +} + +fn validate_proc_macro(target: &TomlTarget, kind: &str, warnings: &mut Vec<String>) { + if target.proc_macro_raw.is_some() && target.proc_macro_raw2.is_some() { + warn_on_deprecated( + "proc-macro", + name_or_panic(target), + format!("{kind} target").as_str(), + warnings, + ); + } +} + +fn validate_crate_types(target: &TomlTarget, kind: &str, warnings: &mut Vec<String>) { + if target.crate_type.is_some() && target.crate_type2.is_some() { + warn_on_deprecated( + "crate-type", + name_or_panic(target), + format!("{kind} target").as_str(), + warnings, + ); + } +} diff --git a/src/tools/cargo/src/cargo/util/toml_mut/dependency.rs b/src/tools/cargo/src/cargo/util/toml_mut/dependency.rs index 2f39b7ab4..88298fa8d 100644 --- a/src/tools/cargo/src/cargo/util/toml_mut/dependency.rs +++ b/src/tools/cargo/src/cargo/util/toml_mut/dependency.rs @@ -464,7 +464,7 @@ impl Dependency { } else if let Some(table) = item.as_table_like_mut() { match &self.source { Some(Source::Registry(src)) => { - table.insert("version", toml_edit::value(src.version.as_str())); + overwrite_value(table, "version", src.version.as_str()); for key in ["path", "git", "branch", "tag", "rev", "workspace"] { table.remove(key); @@ -472,9 +472,9 @@ impl Dependency { } Some(Source::Path(src)) => { let relpath = path_field(crate_root, &src.path); - table.insert("path", toml_edit::value(relpath)); + overwrite_value(table, "path", relpath); if let Some(r) = src.version.as_deref() { - table.insert("version", toml_edit::value(r)); + overwrite_value(table, "version", r); } else { table.remove("version"); } @@ -484,24 +484,24 @@ impl Dependency { } } Some(Source::Git(src)) => { - table.insert("git", toml_edit::value(src.git.as_str())); + overwrite_value(table, "git", src.git.as_str()); if let Some(branch) = src.branch.as_deref() { - table.insert("branch", toml_edit::value(branch)); + overwrite_value(table, "branch", branch); } else { table.remove("branch"); } if let Some(tag) = src.tag.as_deref() { - table.insert("tag", toml_edit::value(tag)); + overwrite_value(table, "tag", tag); } else { table.remove("tag"); } if let Some(rev) = src.rev.as_deref() { - table.insert("rev", toml_edit::value(rev)); + overwrite_value(table, "rev", rev); } else { table.remove("rev"); } if let Some(r) = src.version.as_deref() { - table.insert("version", toml_edit::value(r)); + overwrite_value(table, "version", r); } else { table.remove("version"); } @@ -511,7 +511,7 @@ impl Dependency { } } Some(Source::Workspace(_)) => { - table.insert("workspace", toml_edit::value(true)); + overwrite_value(table, "workspace", true); table.set_dotted(true); key.fmt(); for key in [ @@ -533,7 +533,7 @@ impl Dependency { } if table.contains_key("version") { if let Some(r) = self.registry.as_deref() { - table.insert("registry", toml_edit::value(r)); + overwrite_value(table, "registry", r); } else { table.remove("registry"); } @@ -542,11 +542,11 @@ impl Dependency { } if self.rename.is_some() { - table.insert("package", toml_edit::value(self.name.as_str())); + overwrite_value(table, "package", self.name.as_str()); } match self.default_features { Some(v) => { - table.insert("default-features", toml_edit::value(v)); + overwrite_value(table, "default-features", v); } None => { table.remove("default-features"); @@ -564,29 +564,40 @@ impl Dependency { }) .unwrap_or_default(); features.extend(new_features.iter().map(|s| s.as_str())); - let features = toml_edit::value(features.into_iter().collect::<toml_edit::Value>()); + let features = features.into_iter().collect::<toml_edit::Value>(); table.set_dotted(false); - table.insert("features", features); + overwrite_value(table, "features", features); } else { table.remove("features"); } match self.optional { Some(v) => { table.set_dotted(false); - table.insert("optional", toml_edit::value(v)); + overwrite_value(table, "optional", v); } None => { table.remove("optional"); } } - - table.fmt(); } else { unreachable!("Invalid dependency type: {}", item.type_name()); } } } +fn overwrite_value( + table: &mut dyn toml_edit::TableLike, + key: &str, + value: impl Into<toml_edit::Value>, +) { + let mut value = value.into(); + let existing = table.entry(key).or_insert_with(|| Default::default()); + if let Some(existing_value) = existing.as_value() { + *value.decor_mut() = existing_value.decor().clone(); + } + *existing = toml_edit::Item::Value(value); +} + fn invalid_type(dep: &str, key: &str, actual: &str, expected: &str) -> anyhow::Error { anyhow::format_err!("Found {actual} for {key} when {expected} was expected for {dep}") } diff --git a/src/tools/cargo/src/cargo/util/toml_mut/manifest.rs b/src/tools/cargo/src/cargo/util/toml_mut/manifest.rs index 5529b8029..e859af215 100644 --- a/src/tools/cargo/src/cargo/util/toml_mut/manifest.rs +++ b/src/tools/cargo/src/cargo/util/toml_mut/manifest.rs @@ -296,7 +296,7 @@ impl LocalManifest { let s = self.manifest.data.to_string(); let new_contents_bytes = s.as_bytes(); - cargo_util::paths::write(&self.path, new_contents_bytes) + cargo_util::paths::write_atomic(&self.path, new_contents_bytes) } /// Lookup a dependency. @@ -349,13 +349,16 @@ impl LocalManifest { .get_key_value_mut(dep_key) { dep.update_toml(&crate_root, &mut dep_key, dep_item); + if let Some(table) = dep_item.as_inline_table_mut() { + // So long as we don't have `Cargo.toml` auto-formatting and inline-tables can only + // be on one line, there isn't really much in the way of interesting formatting to + // include (no comments), so let's just wipe it clean + table.fmt(); + } } else { let new_dependency = dep.to_toml(&crate_root); table[dep_key] = new_dependency; } - if let Some(t) = table.as_inline_table_mut() { - t.fmt() - } Ok(()) } @@ -364,17 +367,31 @@ impl LocalManifest { pub fn remove_from_table(&mut self, table_path: &[String], name: &str) -> CargoResult<()> { let parent_table = self.get_table_mut(table_path)?; - let dep = parent_table - .get_mut(name) - .filter(|t| !t.is_none()) - .ok_or_else(|| non_existent_dependency_err(name, table_path.join(".")))?; + match parent_table.get_mut(name).filter(|t| !t.is_none()) { + Some(dep) => { + // remove the dependency + *dep = toml_edit::Item::None; - // remove the dependency - *dep = toml_edit::Item::None; + // remove table if empty + if parent_table.as_table_like().unwrap().is_empty() { + *parent_table = toml_edit::Item::None; + } + } + None => { + // Search in other tables. + let sections = self.get_sections(); + let found_table_path = sections.iter().find_map(|(t, i)| { + let table_path: Vec<String> = + t.to_table().iter().map(|s| s.to_string()).collect(); + i.get(name).is_some().then(|| table_path.join(".")) + }); - // remove table if empty - if parent_table.as_table_like().unwrap().is_empty() { - *parent_table = toml_edit::Item::None; + return Err(non_existent_dependency_err( + name, + table_path.join("."), + found_table_path, + )); + } } Ok(()) @@ -494,12 +511,7 @@ fn fix_feature_activations( // Remove found idx in revers order so we don't invalidate the idx. for idx in remove_list.iter().rev() { - feature_values.remove(*idx); - } - if !remove_list.is_empty() { - // HACK: Instead of cleaning up the users formatting from having removed a feature, we just - // re-format the whole feature list - feature_values.fmt(); + remove_array_index(feature_values, *idx); } if status == DependencyStatus::Required { @@ -539,7 +551,53 @@ fn non_existent_table_err(table: impl std::fmt::Display) -> anyhow::Error { fn non_existent_dependency_err( name: impl std::fmt::Display, - table: impl std::fmt::Display, + search_table: impl std::fmt::Display, + found_table: Option<impl std::fmt::Display>, ) -> anyhow::Error { - anyhow::format_err!("the dependency `{name}` could not be found in `{table}`.") + let mut msg = format!("the dependency `{name}` could not be found in `{search_table}`"); + if let Some(found_table) = found_table { + msg.push_str(&format!("; it is present in `{found_table}`",)); + } + anyhow::format_err!(msg) +} + +fn remove_array_index(array: &mut toml_edit::Array, index: usize) { + let value = array.remove(index); + + // Captures all lines before leading whitespace + let prefix_lines = value + .decor() + .prefix() + .and_then(|p| p.as_str().expect("spans removed").rsplit_once('\n')) + .map(|(lines, _current)| lines); + // Captures all lines after trailing whitespace, before the next comma + let suffix_lines = value + .decor() + .suffix() + .and_then(|p| p.as_str().expect("spans removed").split_once('\n')) + .map(|(_current, lines)| lines); + let mut merged_lines = String::new(); + if let Some(prefix_lines) = prefix_lines { + merged_lines.push_str(prefix_lines); + merged_lines.push('\n'); + } + if let Some(suffix_lines) = suffix_lines { + merged_lines.push_str(suffix_lines); + merged_lines.push('\n'); + } + + let next_index = index; // Since `index` was removed, that effectively auto-advances us + if let Some(next) = array.get_mut(next_index) { + let next_decor = next.decor_mut(); + let next_prefix = next_decor + .prefix() + .map(|s| s.as_str().expect("spans removed")) + .unwrap_or_default(); + merged_lines.push_str(next_prefix); + next_decor.set_prefix(merged_lines); + } else { + let trailing = array.trailing().as_str().expect("spans removed"); + merged_lines.push_str(trailing); + array.set_trailing(merged_lines); + } } diff --git a/src/tools/cargo/src/cargo/util/toml_mut/mod.rs b/src/tools/cargo/src/cargo/util/toml_mut/mod.rs index bdd70e8e6..cb5d3aaf2 100644 --- a/src/tools/cargo/src/cargo/util/toml_mut/mod.rs +++ b/src/tools/cargo/src/cargo/util/toml_mut/mod.rs @@ -11,3 +11,19 @@ pub mod dependency; pub mod manifest; + +// Based on Iterator::is_sorted from nightly std; remove in favor of that when stabilized. +pub fn is_sorted(mut it: impl Iterator<Item = impl PartialOrd>) -> bool { + let Some(mut last) = it.next() else { + return true; + }; + + for curr in it { + if curr < last { + return false; + } + last = curr; + } + + true +} diff --git a/src/tools/cargo/src/cargo/util/workspace.rs b/src/tools/cargo/src/cargo/util/workspace.rs index e8317f101..a2e0fff50 100644 --- a/src/tools/cargo/src/cargo/util/workspace.rs +++ b/src/tools/cargo/src/cargo/util/workspace.rs @@ -87,11 +87,11 @@ pub fn print_available_binaries(ws: &Workspace<'_>, options: &CompileOptions) -> } pub fn print_available_benches(ws: &Workspace<'_>, options: &CompileOptions) -> CargoResult<()> { - print_available_targets(Target::is_bench, ws, options, "--bench", "benches") + print_available_targets(Target::is_bench, ws, options, "--bench", "bench targets") } pub fn print_available_tests(ws: &Workspace<'_>, options: &CompileOptions) -> CargoResult<()> { - print_available_targets(Target::is_test, ws, options, "--test", "tests") + print_available_targets(Target::is_test, ws, options, "--test", "test targets") } /// The path that we pass to rustc is actually fairly important because it will diff --git a/src/tools/cargo/src/cargo/util_semver.rs b/src/tools/cargo/src/cargo/util_semver.rs new file mode 100644 index 000000000..a84c9ee58 --- /dev/null +++ b/src/tools/cargo/src/cargo/util_semver.rs @@ -0,0 +1,195 @@ +use std::fmt::{self, Display}; + +use semver::{Comparator, Op, Version, VersionReq}; +use serde_untagged::UntaggedEnumVisitor; + +pub trait VersionExt { + fn is_prerelease(&self) -> bool; + + fn to_exact_req(&self) -> VersionReq; +} + +impl VersionExt for Version { + fn is_prerelease(&self) -> bool { + !self.pre.is_empty() + } + + fn to_exact_req(&self) -> VersionReq { + VersionReq { + comparators: vec![Comparator { + op: Op::Exact, + major: self.major, + minor: Some(self.minor), + patch: Some(self.patch), + pre: self.pre.clone(), + }], + } + } +} + +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Debug)] +pub struct PartialVersion { + pub major: u64, + pub minor: Option<u64>, + pub patch: Option<u64>, + pub pre: Option<semver::Prerelease>, + pub build: Option<semver::BuildMetadata>, +} + +impl PartialVersion { + pub fn to_version(&self) -> Option<Version> { + Some(Version { + major: self.major, + minor: self.minor?, + patch: self.patch?, + pre: self.pre.clone().unwrap_or_default(), + build: self.build.clone().unwrap_or_default(), + }) + } + + pub fn to_caret_req(&self) -> VersionReq { + VersionReq { + comparators: vec![Comparator { + op: semver::Op::Caret, + major: self.major, + minor: self.minor, + patch: self.patch, + pre: self.pre.as_ref().cloned().unwrap_or_default(), + }], + } + } + + /// Check if this matches a version, including build metadata + /// + /// Build metadata does not affect version precedence but may be necessary for uniquely + /// identifying a package. + pub fn matches(&self, version: &Version) -> bool { + if !version.pre.is_empty() && self.pre.is_none() { + // Pre-release versions must be explicitly opted into, if for no other reason than to + // give us room to figure out and define the semantics + return false; + } + self.major == version.major + && self.minor.map(|f| f == version.minor).unwrap_or(true) + && self.patch.map(|f| f == version.patch).unwrap_or(true) + && self.pre.as_ref().map(|f| f == &version.pre).unwrap_or(true) + && self + .build + .as_ref() + .map(|f| f == &version.build) + .unwrap_or(true) + } +} + +impl From<semver::Version> for PartialVersion { + fn from(ver: semver::Version) -> Self { + let pre = if ver.pre.is_empty() { + None + } else { + Some(ver.pre) + }; + let build = if ver.build.is_empty() { + None + } else { + Some(ver.build) + }; + Self { + major: ver.major, + minor: Some(ver.minor), + patch: Some(ver.patch), + pre, + build, + } + } +} + +impl std::str::FromStr for PartialVersion { + type Err = anyhow::Error; + + fn from_str(value: &str) -> Result<Self, Self::Err> { + if is_req(value) { + anyhow::bail!("unexpected version requirement, expected a version like \"1.32\"") + } + match semver::Version::parse(value) { + Ok(ver) => Ok(ver.into()), + Err(_) => { + // HACK: Leverage `VersionReq` for partial version parsing + let mut version_req = match semver::VersionReq::parse(value) { + Ok(req) => req, + Err(_) if value.contains('-') => { + anyhow::bail!( + "unexpected prerelease field, expected a version like \"1.32\"" + ) + } + Err(_) if value.contains('+') => { + anyhow::bail!("unexpected build field, expected a version like \"1.32\"") + } + Err(_) => anyhow::bail!("expected a version like \"1.32\""), + }; + assert_eq!(version_req.comparators.len(), 1, "guaranteed by is_req"); + let comp = version_req.comparators.pop().unwrap(); + assert_eq!(comp.op, semver::Op::Caret, "guaranteed by is_req"); + let pre = if comp.pre.is_empty() { + None + } else { + Some(comp.pre) + }; + Ok(Self { + major: comp.major, + minor: comp.minor, + patch: comp.patch, + pre, + build: None, + }) + } + } + } +} + +impl Display for PartialVersion { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let major = self.major; + write!(f, "{major}")?; + if let Some(minor) = self.minor { + write!(f, ".{minor}")?; + } + if let Some(patch) = self.patch { + write!(f, ".{patch}")?; + } + if let Some(pre) = self.pre.as_ref() { + write!(f, "-{pre}")?; + } + if let Some(build) = self.build.as_ref() { + write!(f, "+{build}")?; + } + Ok(()) + } +} + +impl serde::Serialize for PartialVersion { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + serializer.collect_str(self) + } +} + +impl<'de> serde::Deserialize<'de> for PartialVersion { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: serde::Deserializer<'de>, + { + UntaggedEnumVisitor::new() + .expecting("SemVer version") + .string(|value| value.parse().map_err(serde::de::Error::custom)) + .deserialize(deserializer) + } +} + +fn is_req(value: &str) -> bool { + let Some(first) = value.chars().next() else { + return false; + }; + "<>=^~".contains(first) || value.contains('*') || value.contains(',') +} diff --git a/src/tools/cargo/src/doc/contrib/book.toml b/src/tools/cargo/src/doc/contrib/book.toml index 628179c0d..e322fd8f3 100644 --- a/src/tools/cargo/src/doc/contrib/book.toml +++ b/src/tools/cargo/src/doc/contrib/book.toml @@ -5,6 +5,8 @@ authors = ["Eric Huss"] [output.html] curly-quotes = true # Enable smart-punctuation feature for more than quotes. git-repository-url = "https://github.com/rust-lang/cargo/tree/master/src/doc/contrib/src" +edit-url-template = "https://github.com/rust-lang/cargo/edit/master/src/doc/contrib/{path}" +search.use-boolean-and = true [output.html.redirect] "/apidoc/cargo/index.html" = "https://doc.rust-lang.org/nightly/nightly-rustc/cargo/" diff --git a/src/tools/cargo/src/doc/contrib/src/SUMMARY.md b/src/tools/cargo/src/doc/contrib/src/SUMMARY.md index f8796b3f3..e97028eb5 100644 --- a/src/tools/cargo/src/doc/contrib/src/SUMMARY.md +++ b/src/tools/cargo/src/doc/contrib/src/SUMMARY.md @@ -11,9 +11,11 @@ - [Design Principles](./design.md) - [Implementing a Change](./implementation/index.md) - [Architecture](./implementation/architecture.md) + - [New packages](./implementation/packages.md) - [New subcommands](./implementation/subcommands.md) - [Console Output](./implementation/console.md) - [Filesystem](./implementation/filesystem.md) + - [Formatting](./implementation/formatting.md) - [Debugging](./implementation/debugging.md) - [Tests](./tests/index.md) - [Running Tests](./tests/running.md) diff --git a/src/tools/cargo/src/doc/contrib/src/implementation/formatting.md b/src/tools/cargo/src/doc/contrib/src/implementation/formatting.md new file mode 100644 index 000000000..91321f1a5 --- /dev/null +++ b/src/tools/cargo/src/doc/contrib/src/implementation/formatting.md @@ -0,0 +1,17 @@ +# Formatting + +When modifying user files, like `Cargo.toml`, we should not change other +sections of the file, +preserving the general formatting. +This includes the table, inline-table, or array that a field is being edited in. + +When adding new entries, they do not need to match the canonical style of the +document but can use the default formatting. +If the entry is already sorted, preserving the sort order is preferred. + +When removing entries, +comments on the same line should be removed but comments on following lines +should be preserved. + +Inconsistencies in style after making a change are left to the user and their +preferred auto-formatter. diff --git a/src/tools/cargo/src/doc/contrib/src/implementation/packages.md b/src/tools/cargo/src/doc/contrib/src/implementation/packages.md new file mode 100644 index 000000000..e7d965eca --- /dev/null +++ b/src/tools/cargo/src/doc/contrib/src/implementation/packages.md @@ -0,0 +1,52 @@ +# New Packages + +This chapter sketches out how to add a new package to the cargo workspace. + +## Steps + +Choose the relevant parent directory +- `credential/` for credential-process related packages +- `benches/` for benchmarking of cargo itself +- `crates/` for everything else + +Run `cargo new <name>` +- `<name>`: + - We tend to use `-` over `_` + - For internal APIs, to avoid collisions with third-party subcommands, we can use the `cargo-util-` prefix + - For xtasks, we use the `xtask-` prefix +- `package.rust-version` + - Internal packages tend to have a policy of "latest" with a [`# MSRV:1` comment](#msrv-policy) + - Ecosystem packages tend to have a policy of "N-2" with a [`# MSRV:3` comment](#msrv-policy) + - If the right choice is inherited from the workspace, feel free to keep it that way +- If running without [cargo new automatically adding to workspace](https://github.com/rust-lang/cargo/pull/12779), add it as a workspace member if not already captured by a glob + +If its an xtask, +- Add it to `.cargo/config.toml`s `[alias]` table +- Mark `package.publish = false` + +If needed to be published with `cargo`, +add the package to `publish.py` in the repo root, +in dependency order. + +Note: by adding the package to the workspace, you automatically get +- CI running `cargo test` +- CI verifying MSRV +- CI checking for `cargo doc` warnings + +## MSRV Policy + +Our MSRV policies are +- Internal packages: support latest version +- Ecosystem packages: support latest 3 versions + +We proactively update the MSRV +- So contributors don't shy away from using newer features, either assuming they + can't ask or feeling like they have to have a justification when asking +- To avoid a de facto MSRV developing from staying on a version for a long + period of time, leaving users unhappy when their expectations aren't met + +To proactively update the MSRV, we use [RenovateBot](https://docs.renovatebot.com/) +with the configuration file in `.github/renovatebot.json5`. +To know what MSRV policy to use, +it looks for comments of the form `# MSRV:N`, +where `N` is the number of supported rust versions. diff --git a/src/tools/cargo/src/doc/contrib/src/process/index.md b/src/tools/cargo/src/doc/contrib/src/process/index.md index c9dae918c..b5cfa5e46 100644 --- a/src/tools/cargo/src/doc/contrib/src/process/index.md +++ b/src/tools/cargo/src/doc/contrib/src/process/index.md @@ -8,11 +8,6 @@ process. Please read the guidelines below before working on an issue or new feature. -**Due to limited review capacity, the Cargo team is not accepting new features -or major changes at this time. Please consult with the team before opening a -new PR. Only issues that have been explicitly marked as accepted will be -reviewed.** - [Working on Cargo]: working-on-cargo.md ## Mentorship @@ -30,8 +25,8 @@ an overview of the things the team is interested in and thinking about. The [RFC Project Board] is used for tracking [RFCs]. [the 2020 roadmap]: https://blog.rust-lang.org/inside-rust/2020/01/10/cargo-in-2020.html -[Roadmap Project Board]: https://github.com/rust-lang/cargo/projects/1 -[RFC Project Board]: https://github.com/rust-lang/cargo/projects/2 +[Roadmap Project Board]: https://github.com/orgs/rust-lang/projects/37 +[RFC Project Board]: https://github.com/orgs/rust-lang/projects/36 [RFCs]: https://github.com/rust-lang/rfcs/ ## Working on issues diff --git a/src/tools/cargo/src/doc/contrib/src/process/working-on-cargo.md b/src/tools/cargo/src/doc/contrib/src/process/working-on-cargo.md index 1567197c4..68f20db38 100644 --- a/src/tools/cargo/src/doc/contrib/src/process/working-on-cargo.md +++ b/src/tools/cargo/src/doc/contrib/src/process/working-on-cargo.md @@ -17,10 +17,12 @@ We encourage people to discuss their design before hacking on code. This gives the Cargo team a chance to know your idea more. Sometimes after a discussion, we even find a way to solve the problem without coding! Typically, you [file an issue] or start a thread on the [internals forum] before submitting a -pull request. Please read [the process] of how features and bugs are managed in -Cargo. +pull request. -## Checkout out the source +Please read [the process] of how features and bugs are managed in Cargo. +**Only issues that have been explicitly marked as [accepted] will be reviewed.** + +## Checkout the source We use the "fork and pull" model [described here][development-models], where contributors push changes to their personal fork and [create pull requests] to @@ -170,3 +172,4 @@ more information on how Cargo releases are made. [internals forum]: https://internals.rust-lang.org/c/tools-and-infrastructure/cargo [file an issue]: https://github.com/rust-lang/cargo/issues [the process]: index.md +[accepted]: https://github.com/rust-lang/cargo/issues?q=is%3Aissue+is%3Aopen+label%3AS-accepted diff --git a/src/tools/cargo/src/doc/man/cargo-add.md b/src/tools/cargo/src/doc/man/cargo-add.md index 21c222a39..d3a62900a 100644 --- a/src/tools/cargo/src/doc/man/cargo-add.md +++ b/src/tools/cargo/src/doc/man/cargo-add.md @@ -35,7 +35,7 @@ When you add a package that is already present, the existing entry will be updat Upon successful invocation, the enabled (`+`) and disabled (`-`) [features] of the specified dependency will be listed in the command's output. -[features]: ../reference/features.md +[features]: ../reference/features.html ## OPTIONS diff --git a/src/tools/cargo/src/doc/man/cargo-bench.md b/src/tools/cargo/src/doc/man/cargo-bench.md index a2a602847..5b1c6ba78 100644 --- a/src/tools/cargo/src/doc/man/cargo-bench.md +++ b/src/tools/cargo/src/doc/man/cargo-bench.md @@ -34,7 +34,7 @@ Benchmarks are built with the `--test` option to `rustc` which creates a special executable by linking your code with libtest. The executable automatically runs all functions annotated with the `#[bench]` attribute. Cargo passes the `--bench` flag to the test harness to tell it to run -only benchmarks. +only benchmarks, regardless of whether the harness is libtest or a custom harness. The libtest harness may be disabled by setting `harness = false` in the target manifest settings, in which case your code will need to provide its own `main` diff --git a/src/tools/cargo/src/doc/man/cargo-install.md b/src/tools/cargo/src/doc/man/cargo-install.md index 5431bdb79..8c520db0a 100644 --- a/src/tools/cargo/src/doc/man/cargo-install.md +++ b/src/tools/cargo/src/doc/man/cargo-install.md @@ -90,7 +90,7 @@ will be used, beginning discovery at `$PATH/.cargo/config.toml`. {{#option "`--vers` _version_" "`--version` _version_" }} Specify a version to install. This may be a [version -requirement](../reference/specifying-dependencies.md), like `~1.2`, to have Cargo +requirement](../reference/specifying-dependencies.html), like `~1.2`, to have Cargo select the newest version from the given requirement. If the version does not have a requirement operator (such as `^` or `~`), then it must be in the form _MAJOR.MINOR.PATCH_, and will install exactly that version; it is *not* diff --git a/src/tools/cargo/src/doc/man/cargo-login.md b/src/tools/cargo/src/doc/man/cargo-login.md index 197636da8..46681f7ef 100644 --- a/src/tools/cargo/src/doc/man/cargo-login.md +++ b/src/tools/cargo/src/doc/man/cargo-login.md @@ -6,7 +6,7 @@ cargo-login --- Log in to a registry ## SYNOPSIS -`cargo login` [_options_] [_token_] -- [_args_] +`cargo login` [_options_] [_token_] [`--` _args_] ## DESCRIPTION @@ -14,6 +14,8 @@ This command will run a credential provider to save a token so that commands that require authentication, such as {{man "cargo-publish" 1}}, will be automatically authenticated. +All the arguments following the two dashes (`--`) are passed to the credential provider. + For the default `cargo:token` credential provider, the token is saved in `$CARGO_HOME/credentials.toml`. `CARGO_HOME` defaults to `.cargo` in your home directory. diff --git a/src/tools/cargo/src/doc/man/cargo-rustc.md b/src/tools/cargo/src/doc/man/cargo-rustc.md index 279c6dbd1..e60b12e8d 100644 --- a/src/tools/cargo/src/doc/man/cargo-rustc.md +++ b/src/tools/cargo/src/doc/man/cargo-rustc.md @@ -65,7 +65,7 @@ The `rustc` subcommand will treat the following named profiles with special beha * `bench` --- Builds in the same was as the {{man "cargo-bench" 1}} command, similar to the `test` profile. -See the [the reference](../reference/profiles.html) for more details on profiles. +See [the reference](../reference/profiles.html) for more details on profiles. {{/option}} {{> options-ignore-rust-version }} diff --git a/src/tools/cargo/src/doc/man/cargo-vendor.md b/src/tools/cargo/src/doc/man/cargo-vendor.md index b30d0d8dd..169e64db2 100644 --- a/src/tools/cargo/src/doc/man/cargo-vendor.md +++ b/src/tools/cargo/src/doc/man/cargo-vendor.md @@ -16,8 +16,10 @@ the vendor directory specified by `<path>` will contain all remote sources from dependencies specified. Additional manifests beyond the default one can be specified with the `-s` option. -The `cargo vendor` command will also print out the configuration necessary -to use the vendored sources, which you will need to add to `.cargo/config.toml`. +The configuration necessary to use the vendored sources would be printed to +stdout after `cargo vendor` completes the vendoring process. +You will need to add or redirect it to your Cargo configuration file, +which is usually `.cargo/config.toml` locally for the current package. ## OPTIONS @@ -88,6 +90,10 @@ only a subset of the packages have changed. cargo vendor -s ../path/to/Cargo.toml +4. Vendor and redirect the necessary vendor configs to a config file. + + cargo vendor > path/to/my/cargo/config.toml + ## SEE ALSO {{man "cargo" 1}} diff --git a/src/tools/cargo/src/doc/man/generated_txt/cargo-add.txt b/src/tools/cargo/src/doc/man/generated_txt/cargo-add.txt index 6447499dc..3bd4bd5aa 100644 --- a/src/tools/cargo/src/doc/man/generated_txt/cargo-add.txt +++ b/src/tools/cargo/src/doc/man/generated_txt/cargo-add.txt @@ -33,8 +33,8 @@ DESCRIPTION be updated with the flags specified. Upon successful invocation, the enabled (+) and disabled (-) features - <https://doc.rust-lang.org/cargo/reference/features.md> of the specified - dependency will be listed in the command’s output. + <https://doc.rust-lang.org/cargo/reference/features.html> of the + specified dependency will be listed in the command’s output. OPTIONS Source options diff --git a/src/tools/cargo/src/doc/man/generated_txt/cargo-bench.txt b/src/tools/cargo/src/doc/man/generated_txt/cargo-bench.txt index 796fbd11b..a7013a077 100644 --- a/src/tools/cargo/src/doc/man/generated_txt/cargo-bench.txt +++ b/src/tools/cargo/src/doc/man/generated_txt/cargo-bench.txt @@ -27,7 +27,8 @@ DESCRIPTION special executable by linking your code with libtest. The executable automatically runs all functions annotated with the #[bench] attribute. Cargo passes the --bench flag to the test harness to tell it to run only - benchmarks. + benchmarks, regardless of whether the harness is libtest or a custom + harness. The libtest harness may be disabled by setting harness = false in the target manifest settings, in which case your code will need to provide @@ -235,7 +236,7 @@ OPTIONS documentation for more details. --profile name - Benchmark with the given profile. See the the reference + Benchmark with the given profile. See the reference <https://doc.rust-lang.org/cargo/reference/profiles.html> for more details on profiles. diff --git a/src/tools/cargo/src/doc/man/generated_txt/cargo-build.txt b/src/tools/cargo/src/doc/man/generated_txt/cargo-build.txt index 06a7a6b3c..db39e2011 100644 --- a/src/tools/cargo/src/doc/man/generated_txt/cargo-build.txt +++ b/src/tools/cargo/src/doc/man/generated_txt/cargo-build.txt @@ -157,7 +157,7 @@ OPTIONS --profile option for choosing a specific profile by name. --profile name - Build with the given profile. See the the reference + Build with the given profile. See the reference <https://doc.rust-lang.org/cargo/reference/profiles.html> for more details on profiles. diff --git a/src/tools/cargo/src/doc/man/generated_txt/cargo-check.txt b/src/tools/cargo/src/doc/man/generated_txt/cargo-check.txt index b447455ee..5a3ea18f4 100644 --- a/src/tools/cargo/src/doc/man/generated_txt/cargo-check.txt +++ b/src/tools/cargo/src/doc/man/generated_txt/cargo-check.txt @@ -161,7 +161,7 @@ OPTIONS the test cfg option. See rustc tests <https://doc.rust-lang.org/rustc/tests/index.html> for more detail. - See the the reference + See the reference <https://doc.rust-lang.org/cargo/reference/profiles.html> for more details on profiles. diff --git a/src/tools/cargo/src/doc/man/generated_txt/cargo-doc.txt b/src/tools/cargo/src/doc/man/generated_txt/cargo-doc.txt index 773d600c6..f34e08df9 100644 --- a/src/tools/cargo/src/doc/man/generated_txt/cargo-doc.txt +++ b/src/tools/cargo/src/doc/man/generated_txt/cargo-doc.txt @@ -132,7 +132,7 @@ OPTIONS --profile option for choosing a specific profile by name. --profile name - Document with the given profile. See the the reference + Document with the given profile. See the reference <https://doc.rust-lang.org/cargo/reference/profiles.html> for more details on profiles. diff --git a/src/tools/cargo/src/doc/man/generated_txt/cargo-fix.txt b/src/tools/cargo/src/doc/man/generated_txt/cargo-fix.txt index 3e7910ca5..474c2beb3 100644 --- a/src/tools/cargo/src/doc/man/generated_txt/cargo-fix.txt +++ b/src/tools/cargo/src/doc/man/generated_txt/cargo-fix.txt @@ -234,7 +234,7 @@ OPTIONS the test cfg option. See rustc tests <https://doc.rust-lang.org/rustc/tests/index.html> for more detail. - See the the reference + See the reference <https://doc.rust-lang.org/cargo/reference/profiles.html> for more details on profiles. diff --git a/src/tools/cargo/src/doc/man/generated_txt/cargo-init.txt b/src/tools/cargo/src/doc/man/generated_txt/cargo-init.txt index 678024881..5c7e386c3 100644 --- a/src/tools/cargo/src/doc/man/generated_txt/cargo-init.txt +++ b/src/tools/cargo/src/doc/man/generated_txt/cargo-init.txt @@ -31,7 +31,7 @@ OPTIONS --edition edition Specify the Rust edition to use. Default is 2021. Possible values: - 2015, 2018, 2021 + 2015, 2018, 2021, 2024 --name name Set the package name. Defaults to the directory name. diff --git a/src/tools/cargo/src/doc/man/generated_txt/cargo-install.txt b/src/tools/cargo/src/doc/man/generated_txt/cargo-install.txt index 02790f1d0..0b4aece70 100644 --- a/src/tools/cargo/src/doc/man/generated_txt/cargo-install.txt +++ b/src/tools/cargo/src/doc/man/generated_txt/cargo-install.txt @@ -94,7 +94,7 @@ OPTIONS Install Options --vers version, --version version Specify a version to install. This may be a version requirement - <https://doc.rust-lang.org/cargo/reference/specifying-dependencies.md>, + <https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html>, like ~1.2, to have Cargo select the newest version from the given requirement. If the version does not have a requirement operator (such as ^ or ~), then it must be in the form MAJOR.MINOR.PATCH, and @@ -212,7 +212,7 @@ OPTIONS the --profile option for choosing a specific profile by name. --profile name - Install with the given profile. See the the reference + Install with the given profile. See the reference <https://doc.rust-lang.org/cargo/reference/profiles.html> for more details on profiles. diff --git a/src/tools/cargo/src/doc/man/generated_txt/cargo-login.txt b/src/tools/cargo/src/doc/man/generated_txt/cargo-login.txt index ae8127fc9..585a7bf2e 100644 --- a/src/tools/cargo/src/doc/man/generated_txt/cargo-login.txt +++ b/src/tools/cargo/src/doc/man/generated_txt/cargo-login.txt @@ -4,13 +4,16 @@ NAME cargo-login — Log in to a registry SYNOPSIS - cargo login [options] [token] – [args] + cargo login [options] [token] [-- args] DESCRIPTION This command will run a credential provider to save a token so that commands that require authentication, such as cargo-publish(1), will be automatically authenticated. + All the arguments following the two dashes (--) are passed to the + credential provider. + For the default cargo:token credential provider, the token is saved in $CARGO_HOME/credentials.toml. CARGO_HOME defaults to .cargo in your home directory. diff --git a/src/tools/cargo/src/doc/man/generated_txt/cargo-new.txt b/src/tools/cargo/src/doc/man/generated_txt/cargo-new.txt index 5d2c61b48..5f5ebbfbd 100644 --- a/src/tools/cargo/src/doc/man/generated_txt/cargo-new.txt +++ b/src/tools/cargo/src/doc/man/generated_txt/cargo-new.txt @@ -26,7 +26,7 @@ OPTIONS --edition edition Specify the Rust edition to use. Default is 2021. Possible values: - 2015, 2018, 2021 + 2015, 2018, 2021, 2024 --name name Set the package name. Defaults to the directory name. diff --git a/src/tools/cargo/src/doc/man/generated_txt/cargo-run.txt b/src/tools/cargo/src/doc/man/generated_txt/cargo-run.txt index 495a08a6c..00e629337 100644 --- a/src/tools/cargo/src/doc/man/generated_txt/cargo-run.txt +++ b/src/tools/cargo/src/doc/man/generated_txt/cargo-run.txt @@ -80,7 +80,7 @@ OPTIONS --profile option for choosing a specific profile by name. --profile name - Run with the given profile. See the the reference + Run with the given profile. See the reference <https://doc.rust-lang.org/cargo/reference/profiles.html> for more details on profiles. diff --git a/src/tools/cargo/src/doc/man/generated_txt/cargo-rustc.txt b/src/tools/cargo/src/doc/man/generated_txt/cargo-rustc.txt index af6ad9d59..866361e93 100644 --- a/src/tools/cargo/src/doc/man/generated_txt/cargo-rustc.txt +++ b/src/tools/cargo/src/doc/man/generated_txt/cargo-rustc.txt @@ -165,7 +165,7 @@ OPTIONS o bench — Builds in the same was as the cargo-bench(1) command, similar to the test profile. - See the the reference + See the reference <https://doc.rust-lang.org/cargo/reference/profiles.html> for more details on profiles. diff --git a/src/tools/cargo/src/doc/man/generated_txt/cargo-rustdoc.txt b/src/tools/cargo/src/doc/man/generated_txt/cargo-rustdoc.txt index 5ba200644..3237c4cae 100644 --- a/src/tools/cargo/src/doc/man/generated_txt/cargo-rustdoc.txt +++ b/src/tools/cargo/src/doc/man/generated_txt/cargo-rustdoc.txt @@ -148,7 +148,7 @@ OPTIONS --profile option for choosing a specific profile by name. --profile name - Document with the given profile. See the the reference + Document with the given profile. See the reference <https://doc.rust-lang.org/cargo/reference/profiles.html> for more details on profiles. diff --git a/src/tools/cargo/src/doc/man/generated_txt/cargo-test.txt b/src/tools/cargo/src/doc/man/generated_txt/cargo-test.txt index b992d0d2f..598c8f9af 100644 --- a/src/tools/cargo/src/doc/man/generated_txt/cargo-test.txt +++ b/src/tools/cargo/src/doc/man/generated_txt/cargo-test.txt @@ -262,7 +262,7 @@ OPTIONS --profile option for choosing a specific profile by name. --profile name - Test with the given profile. See the the reference + Test with the given profile. See the reference <https://doc.rust-lang.org/cargo/reference/profiles.html> for more details on profiles. diff --git a/src/tools/cargo/src/doc/man/generated_txt/cargo-vendor.txt b/src/tools/cargo/src/doc/man/generated_txt/cargo-vendor.txt index c325b7534..f0314aedb 100644 --- a/src/tools/cargo/src/doc/man/generated_txt/cargo-vendor.txt +++ b/src/tools/cargo/src/doc/man/generated_txt/cargo-vendor.txt @@ -13,9 +13,10 @@ DESCRIPTION remote sources from dependencies specified. Additional manifests beyond the default one can be specified with the -s option. - The cargo vendor command will also print out the configuration necessary - to use the vendored sources, which you will need to add to - .cargo/config.toml. + The configuration necessary to use the vendored sources would be printed + to stdout after cargo vendor completes the vendoring process. You will + need to add or redirect it to your Cargo configuration file, which is + usually .cargo/config.toml locally for the current package. OPTIONS Vendor Options @@ -157,6 +158,10 @@ EXAMPLES cargo vendor -s ../path/to/Cargo.toml + 4. Vendor and redirect the necessary vendor configs to a config file. + + cargo vendor > path/to/my/cargo/config.toml + SEE ALSO cargo(1) diff --git a/src/tools/cargo/src/doc/man/includes/options-new.md b/src/tools/cargo/src/doc/man/includes/options-new.md index e9792f05e..0b39ae34f 100644 --- a/src/tools/cargo/src/doc/man/includes/options-new.md +++ b/src/tools/cargo/src/doc/man/includes/options-new.md @@ -11,7 +11,7 @@ Create a package with a library target (`src/lib.rs`). {{#option "`--edition` _edition_" }} Specify the Rust edition to use. Default is 2021. -Possible values: 2015, 2018, 2021 +Possible values: 2015, 2018, 2021, 2024 {{/option}} {{#option "`--name` _name_" }} diff --git a/src/tools/cargo/src/doc/man/includes/options-profile-legacy-check.md b/src/tools/cargo/src/doc/man/includes/options-profile-legacy-check.md index 0ec82e693..cb38df0f9 100644 --- a/src/tools/cargo/src/doc/man/includes/options-profile-legacy-check.md +++ b/src/tools/cargo/src/doc/man/includes/options-profile-legacy-check.md @@ -6,5 +6,5 @@ test mode which will enable checking tests and enable the `test` cfg option. See [rustc tests](https://doc.rust-lang.org/rustc/tests/index.html) for more detail. -See the [the reference](../reference/profiles.html) for more details on profiles. +See [the reference](../reference/profiles.html) for more details on profiles. {{/option}} diff --git a/src/tools/cargo/src/doc/man/includes/options-profile.md b/src/tools/cargo/src/doc/man/includes/options-profile.md index 2452e7b14..242ca2254 100644 --- a/src/tools/cargo/src/doc/man/includes/options-profile.md +++ b/src/tools/cargo/src/doc/man/includes/options-profile.md @@ -1,4 +1,4 @@ {{#option "`--profile` _name_" }} {{actionverb}} with the given profile. -See the [the reference](../reference/profiles.html) for more details on profiles. +See [the reference](../reference/profiles.html) for more details on profiles. {{/option}} diff --git a/src/tools/cargo/src/doc/src/commands/cargo-add.md b/src/tools/cargo/src/doc/src/commands/cargo-add.md index 03485e121..175111495 100644 --- a/src/tools/cargo/src/doc/src/commands/cargo-add.md +++ b/src/tools/cargo/src/doc/src/commands/cargo-add.md @@ -32,7 +32,7 @@ When you add a package that is already present, the existing entry will be updat Upon successful invocation, the enabled (`+`) and disabled (`-`) [features] of the specified dependency will be listed in the command's output. -[features]: ../reference/features.md +[features]: ../reference/features.html ## OPTIONS diff --git a/src/tools/cargo/src/doc/src/commands/cargo-bench.md b/src/tools/cargo/src/doc/src/commands/cargo-bench.md index 45584023e..d50b35f11 100644 --- a/src/tools/cargo/src/doc/src/commands/cargo-bench.md +++ b/src/tools/cargo/src/doc/src/commands/cargo-bench.md @@ -30,7 +30,7 @@ Benchmarks are built with the `--test` option to `rustc` which creates a special executable by linking your code with libtest. The executable automatically runs all functions annotated with the `#[bench]` attribute. Cargo passes the `--bench` flag to the test harness to tell it to run -only benchmarks. +only benchmarks, regardless of whether the harness is libtest or a custom harness. The libtest harness may be disabled by setting `harness = false` in the target manifest settings, in which case your code will need to provide its own `main` @@ -278,7 +278,7 @@ target artifacts are placed in a separate directory. See the <dt class="option-term" id="option-cargo-bench---profile"><a class="option-anchor" href="#option-cargo-bench---profile"></a><code>--profile</code> <em>name</em></dt> <dd class="option-desc">Benchmark with the given profile. -See the <a href="../reference/profiles.html">the reference</a> for more details on profiles.</dd> +See <a href="../reference/profiles.html">the reference</a> for more details on profiles.</dd> diff --git a/src/tools/cargo/src/doc/src/commands/cargo-build.md b/src/tools/cargo/src/doc/src/commands/cargo-build.md index 8e517bd1f..70c38a05b 100644 --- a/src/tools/cargo/src/doc/src/commands/cargo-build.md +++ b/src/tools/cargo/src/doc/src/commands/cargo-build.md @@ -199,7 +199,7 @@ See also the <code>--profile</code> option for choosing a specific profile by na <dt class="option-term" id="option-cargo-build---profile"><a class="option-anchor" href="#option-cargo-build---profile"></a><code>--profile</code> <em>name</em></dt> <dd class="option-desc">Build with the given profile. -See the <a href="../reference/profiles.html">the reference</a> for more details on profiles.</dd> +See <a href="../reference/profiles.html">the reference</a> for more details on profiles.</dd> diff --git a/src/tools/cargo/src/doc/src/commands/cargo-check.md b/src/tools/cargo/src/doc/src/commands/cargo-check.md index 3d7d0a490..1bb0f85c1 100644 --- a/src/tools/cargo/src/doc/src/commands/cargo-check.md +++ b/src/tools/cargo/src/doc/src/commands/cargo-check.md @@ -198,7 +198,7 @@ See also the <code>--profile</code> option for choosing a specific profile by na test mode which will enable checking tests and enable the <code>test</code> cfg option. See <a href="https://doc.rust-lang.org/rustc/tests/index.html">rustc tests</a> for more detail.</p> -<p>See the <a href="../reference/profiles.html">the reference</a> for more details on profiles.</dd> +<p>See <a href="../reference/profiles.html">the reference</a> for more details on profiles.</dd> diff --git a/src/tools/cargo/src/doc/src/commands/cargo-doc.md b/src/tools/cargo/src/doc/src/commands/cargo-doc.md index 92843838c..aebb04c9d 100644 --- a/src/tools/cargo/src/doc/src/commands/cargo-doc.md +++ b/src/tools/cargo/src/doc/src/commands/cargo-doc.md @@ -172,7 +172,7 @@ See also the <code>--profile</code> option for choosing a specific profile by na <dt class="option-term" id="option-cargo-doc---profile"><a class="option-anchor" href="#option-cargo-doc---profile"></a><code>--profile</code> <em>name</em></dt> <dd class="option-desc">Document with the given profile. -See the <a href="../reference/profiles.html">the reference</a> for more details on profiles.</dd> +See <a href="../reference/profiles.html">the reference</a> for more details on profiles.</dd> diff --git a/src/tools/cargo/src/doc/src/commands/cargo-fix.md b/src/tools/cargo/src/doc/src/commands/cargo-fix.md index 4dde83d96..9211cf7f8 100644 --- a/src/tools/cargo/src/doc/src/commands/cargo-fix.md +++ b/src/tools/cargo/src/doc/src/commands/cargo-fix.md @@ -278,7 +278,7 @@ See also the <code>--profile</code> option for choosing a specific profile by na test mode which will enable checking tests and enable the <code>test</code> cfg option. See <a href="https://doc.rust-lang.org/rustc/tests/index.html">rustc tests</a> for more detail.</p> -<p>See the <a href="../reference/profiles.html">the reference</a> for more details on profiles.</dd> +<p>See <a href="../reference/profiles.html">the reference</a> for more details on profiles.</dd> diff --git a/src/tools/cargo/src/doc/src/commands/cargo-init.md b/src/tools/cargo/src/doc/src/commands/cargo-init.md index c0cf34b51..70b54802b 100644 --- a/src/tools/cargo/src/doc/src/commands/cargo-init.md +++ b/src/tools/cargo/src/doc/src/commands/cargo-init.md @@ -40,7 +40,7 @@ This is the default behavior.</dd> <dt class="option-term" id="option-cargo-init---edition"><a class="option-anchor" href="#option-cargo-init---edition"></a><code>--edition</code> <em>edition</em></dt> <dd class="option-desc">Specify the Rust edition to use. Default is 2021. -Possible values: 2015, 2018, 2021</dd> +Possible values: 2015, 2018, 2021, 2024</dd> <dt class="option-term" id="option-cargo-init---name"><a class="option-anchor" href="#option-cargo-init---name"></a><code>--name</code> <em>name</em></dt> diff --git a/src/tools/cargo/src/doc/src/commands/cargo-install.md b/src/tools/cargo/src/doc/src/commands/cargo-install.md index 5640af87a..db0ff10db 100644 --- a/src/tools/cargo/src/doc/src/commands/cargo-install.md +++ b/src/tools/cargo/src/doc/src/commands/cargo-install.md @@ -94,7 +94,7 @@ will be used, beginning discovery at `$PATH/.cargo/config.toml`. <dt class="option-term" id="option-cargo-install---vers"><a class="option-anchor" href="#option-cargo-install---vers"></a><code>--vers</code> <em>version</em></dt> <dt class="option-term" id="option-cargo-install---version"><a class="option-anchor" href="#option-cargo-install---version"></a><code>--version</code> <em>version</em></dt> -<dd class="option-desc">Specify a version to install. This may be a <a href="../reference/specifying-dependencies.md">version +<dd class="option-desc">Specify a version to install. This may be a <a href="../reference/specifying-dependencies.html">version requirement</a>, like <code>~1.2</code>, to have Cargo select the newest version from the given requirement. If the version does not have a requirement operator (such as <code>^</code> or <code>~</code>), then it must be in the form @@ -242,7 +242,7 @@ See also the <code>--profile</code> option for choosing a specific profile by na <dt class="option-term" id="option-cargo-install---profile"><a class="option-anchor" href="#option-cargo-install---profile"></a><code>--profile</code> <em>name</em></dt> <dd class="option-desc">Install with the given profile. -See the <a href="../reference/profiles.html">the reference</a> for more details on profiles.</dd> +See <a href="../reference/profiles.html">the reference</a> for more details on profiles.</dd> diff --git a/src/tools/cargo/src/doc/src/commands/cargo-login.md b/src/tools/cargo/src/doc/src/commands/cargo-login.md index e2efcfe4f..ffdacfc5a 100644 --- a/src/tools/cargo/src/doc/src/commands/cargo-login.md +++ b/src/tools/cargo/src/doc/src/commands/cargo-login.md @@ -6,7 +6,7 @@ cargo-login --- Log in to a registry ## SYNOPSIS -`cargo login` [_options_] [_token_] -- [_args_] +`cargo login` [_options_] [_token_] [`--` _args_] ## DESCRIPTION @@ -14,6 +14,8 @@ This command will run a credential provider to save a token so that commands that require authentication, such as [cargo-publish(1)](cargo-publish.html), will be automatically authenticated. +All the arguments following the two dashes (`--`) are passed to the credential provider. + For the default `cargo:token` credential provider, the token is saved in `$CARGO_HOME/credentials.toml`. `CARGO_HOME` defaults to `.cargo` in your home directory. diff --git a/src/tools/cargo/src/doc/src/commands/cargo-new.md b/src/tools/cargo/src/doc/src/commands/cargo-new.md index 144b6f2eb..4e9da6715 100644 --- a/src/tools/cargo/src/doc/src/commands/cargo-new.md +++ b/src/tools/cargo/src/doc/src/commands/cargo-new.md @@ -35,7 +35,7 @@ This is the default behavior.</dd> <dt class="option-term" id="option-cargo-new---edition"><a class="option-anchor" href="#option-cargo-new---edition"></a><code>--edition</code> <em>edition</em></dt> <dd class="option-desc">Specify the Rust edition to use. Default is 2021. -Possible values: 2015, 2018, 2021</dd> +Possible values: 2015, 2018, 2021, 2024</dd> <dt class="option-term" id="option-cargo-new---name"><a class="option-anchor" href="#option-cargo-new---name"></a><code>--name</code> <em>name</em></dt> diff --git a/src/tools/cargo/src/doc/src/commands/cargo-run.md b/src/tools/cargo/src/doc/src/commands/cargo-run.md index c14ad77dc..8c24b8352 100644 --- a/src/tools/cargo/src/doc/src/commands/cargo-run.md +++ b/src/tools/cargo/src/doc/src/commands/cargo-run.md @@ -111,7 +111,7 @@ See also the <code>--profile</code> option for choosing a specific profile by na <dt class="option-term" id="option-cargo-run---profile"><a class="option-anchor" href="#option-cargo-run---profile"></a><code>--profile</code> <em>name</em></dt> <dd class="option-desc">Run with the given profile. -See the <a href="../reference/profiles.html">the reference</a> for more details on profiles.</dd> +See <a href="../reference/profiles.html">the reference</a> for more details on profiles.</dd> diff --git a/src/tools/cargo/src/doc/src/commands/cargo-rustc.md b/src/tools/cargo/src/doc/src/commands/cargo-rustc.md index 6ae52d965..f58c8fdda 100644 --- a/src/tools/cargo/src/doc/src/commands/cargo-rustc.md +++ b/src/tools/cargo/src/doc/src/commands/cargo-rustc.md @@ -197,7 +197,7 @@ tests</a> for more detail.</li> <li><code>bench</code> — Builds in the same was as the <a href="cargo-bench.html">cargo-bench(1)</a> command, similar to the <code>test</code> profile.</li> </ul> -<p>See the <a href="../reference/profiles.html">the reference</a> for more details on profiles.</dd> +<p>See <a href="../reference/profiles.html">the reference</a> for more details on profiles.</dd> <dt class="option-term" id="option-cargo-rustc---ignore-rust-version"><a class="option-anchor" href="#option-cargo-rustc---ignore-rust-version"></a><code>--ignore-rust-version</code></dt> diff --git a/src/tools/cargo/src/doc/src/commands/cargo-rustdoc.md b/src/tools/cargo/src/doc/src/commands/cargo-rustdoc.md index 6635cded5..2e0e67d97 100644 --- a/src/tools/cargo/src/doc/src/commands/cargo-rustdoc.md +++ b/src/tools/cargo/src/doc/src/commands/cargo-rustdoc.md @@ -191,7 +191,7 @@ See also the <code>--profile</code> option for choosing a specific profile by na <dt class="option-term" id="option-cargo-rustdoc---profile"><a class="option-anchor" href="#option-cargo-rustdoc---profile"></a><code>--profile</code> <em>name</em></dt> <dd class="option-desc">Document with the given profile. -See the <a href="../reference/profiles.html">the reference</a> for more details on profiles.</dd> +See <a href="../reference/profiles.html">the reference</a> for more details on profiles.</dd> diff --git a/src/tools/cargo/src/doc/src/commands/cargo-test.md b/src/tools/cargo/src/doc/src/commands/cargo-test.md index 6a6ae82d2..ef978570e 100644 --- a/src/tools/cargo/src/doc/src/commands/cargo-test.md +++ b/src/tools/cargo/src/doc/src/commands/cargo-test.md @@ -307,7 +307,7 @@ See also the <code>--profile</code> option for choosing a specific profile by na <dt class="option-term" id="option-cargo-test---profile"><a class="option-anchor" href="#option-cargo-test---profile"></a><code>--profile</code> <em>name</em></dt> <dd class="option-desc">Test with the given profile. -See the <a href="../reference/profiles.html">the reference</a> for more details on profiles.</dd> +See <a href="../reference/profiles.html">the reference</a> for more details on profiles.</dd> diff --git a/src/tools/cargo/src/doc/src/commands/cargo-vendor.md b/src/tools/cargo/src/doc/src/commands/cargo-vendor.md index cf47fc256..84560a688 100644 --- a/src/tools/cargo/src/doc/src/commands/cargo-vendor.md +++ b/src/tools/cargo/src/doc/src/commands/cargo-vendor.md @@ -16,8 +16,10 @@ the vendor directory specified by `<path>` will contain all remote sources from dependencies specified. Additional manifests beyond the default one can be specified with the `-s` option. -The `cargo vendor` command will also print out the configuration necessary -to use the vendored sources, which you will need to add to `.cargo/config.toml`. +The configuration necessary to use the vendored sources would be printed to +stdout after `cargo vendor` completes the vendoring process. +You will need to add or redirect it to your Cargo configuration file, +which is usually `.cargo/config.toml` locally for the current package. ## OPTIONS @@ -189,6 +191,10 @@ details on environment variables that Cargo reads. cargo vendor -s ../path/to/Cargo.toml +4. Vendor and redirect the necessary vendor configs to a config file. + + cargo vendor > path/to/my/cargo/config.toml + ## SEE ALSO [cargo(1)](cargo.html) diff --git a/src/tools/cargo/src/doc/src/reference/config.md b/src/tools/cargo/src/doc/src/reference/config.md index 6a479b81b..25e17ac90 100644 --- a/src/tools/cargo/src/doc/src/reference/config.md +++ b/src/tools/cargo/src/doc/src/reference/config.md @@ -126,6 +126,7 @@ inherits = "dev" # Inherits settings from [profile.dev]. opt-level = 0 # Optimization level. debug = true # Include debug info. split-debuginfo = '...' # Debug info splitting behavior. +strip = "none" # Removes symbols or debuginfo. debug-assertions = true # Enables debug assertions. overflow-checks = true # Enables runtime integer overflow checks. lto = false # Sets link-time optimization. @@ -133,7 +134,6 @@ panic = 'unwind' # The panic strategy. incremental = true # Incremental compilation. codegen-units = 16 # Number of code generation units. rpath = false # Sets the rpath linking option. -strip = "none" # Removes symbols or debuginfo. [profile.<name>.build-override] # Overrides build-script settings. # Same keys for a normal profile. [profile.<name>.package.<name>] # Override profile for a package. @@ -180,6 +180,7 @@ metadata_key2 = "value" quiet = false # whether cargo output is quiet verbose = false # whether cargo provides verbose output color = 'auto' # whether cargo colorizes output +hyperlinks = true # whether cargo inserts links into output progress.when = 'auto' # whether cargo shows progress bar progress.width = 80 # width of progress bar ``` @@ -889,6 +890,13 @@ See [debug](profiles.md#debug). See [split-debuginfo](profiles.md#split-debuginfo). +#### `profile.<name>.strip` +* Type: string or boolean +* Default: See profile docs. +* Environment: `CARGO_PROFILE_<name>_STRIP` + +See [strip](profiles.md#strip). + #### `profile.<name>.debug-assertions` * Type: boolean * Default: See profile docs. @@ -926,21 +934,21 @@ See [opt-level](profiles.md#opt-level). #### `profile.<name>.panic` * Type: string -* default: See profile docs. +* Default: See profile docs. * Environment: `CARGO_PROFILE_<name>_PANIC` See [panic](profiles.md#panic). #### `profile.<name>.rpath` * Type: boolean -* default: See profile docs. +* Default: See profile docs. * Environment: `CARGO_PROFILE_<name>_RPATH` See [rpath](profiles.md#rpath). #### `profile.<name>.strip` * Type: string -* default: See profile docs. +* Default: See profile docs. * Environment: `CARGO_PROFILE_<name>_STRIP` See [strip](profiles.md#strip). @@ -1264,6 +1272,13 @@ Controls whether or not colored output is used in the terminal. Possible values: Can be overridden with the `--color` command-line option. +#### `term.hyperlinks` +* Type: bool +* Default: auto-detect +* Environment: `CARGO_TERM_HYPERLINKS` + +Controls whether or not hyperlinks are used in the terminal. + #### `term.progress.when` * Type: string * Default: "auto" diff --git a/src/tools/cargo/src/doc/src/reference/environment-variables.md b/src/tools/cargo/src/doc/src/reference/environment-variables.md index 37c788f8d..3f0552613 100644 --- a/src/tools/cargo/src/doc/src/reference/environment-variables.md +++ b/src/tools/cargo/src/doc/src/reference/environment-variables.md @@ -396,7 +396,7 @@ let out_dir = env::var("OUT_DIR").unwrap(); * `CARGO_PKG_<var>` --- The package information variables, with the same names and values as are [provided during crate building][variables set for crates]. [`tracing`]: https://docs.rs/tracing -[debug logging]: https://doc.crates.io/contrib/architecture/console.html#debug-logging +[debug logging]: https://doc.crates.io/contrib/implementation/debugging.html#logging [unix-like platforms]: ../../reference/conditional-compilation.html#unix-and-windows [windows-like platforms]: ../../reference/conditional-compilation.html#unix-and-windows [target family]: ../../reference/conditional-compilation.html#target_family diff --git a/src/tools/cargo/src/doc/src/reference/features.md b/src/tools/cargo/src/doc/src/reference/features.md index 9e521049c..e3a845d95 100644 --- a/src/tools/cargo/src/doc/src/reference/features.md +++ b/src/tools/cargo/src/doc/src/reference/features.md @@ -7,6 +7,13 @@ either be enabled or disabled. Features for the package being built can be enabled on the command-line with flags such as `--features`. Features for dependencies can be enabled in the dependency declaration in `Cargo.toml`. +> **Note**: New crates or versions published on crates.io are now limited to +> a maximum of 300 features. Exceptions are granted on a case-by-case basis. +> See this [blog post] for details. Participation in solution discussions is +> encouraged via the crates.io Zulip stream. + +[blog post]: https://blog.rust-lang.org/2023/10/26/broken-badges-and-23k-keywords.html + See also the [Features Examples] chapter for some examples of how features can be used. diff --git a/src/tools/cargo/src/doc/src/reference/manifest.md b/src/tools/cargo/src/doc/src/reference/manifest.md index 5ecbe5117..e3168a47f 100644 --- a/src/tools/cargo/src/doc/src/reference/manifest.md +++ b/src/tools/cargo/src/doc/src/reference/manifest.md @@ -109,6 +109,8 @@ resolve dependencies, and for guidelines on setting your own version. See the [SemVer compatibility] chapter for more details on exactly what constitutes a breaking change. +This field is optional and defaults to `0.0.0`. The field is required for publishing packages. + [Resolver]: resolver.md [SemVer compatibility]: semver.md @@ -258,9 +260,9 @@ The `license` field contains the name of the software license that the package is released under. The `license-file` field contains the path to a file containing the text of the license (relative to this `Cargo.toml`). -[crates.io] interprets the `license` field as an [SPDX 2.1 license -expression][spdx-2.1-license-expressions]. The name must be a known license -from the [SPDX license list 3.11][spdx-license-list-3.11]. Parentheses are not +[crates.io] interprets the `license` field as an [SPDX 2.3 license +expression][spdx-2.3-license-expressions]. The name must be a known license +from the [SPDX license list 3.20][spdx-license-list-3.20]. Parentheses are not currently supported. See the [SPDX site] for more information. SPDX license expressions support AND and OR operators to combine multiple @@ -470,23 +472,22 @@ if any of those files change. ### The `publish` field -The `publish` field can be used to prevent a package from being published to a -package registry (like *crates.io*) by mistake, for instance to keep a package -private in a company. - +The `publish` field can be used to control which registries names the package +may be published to: ```toml [package] # ... -publish = false +publish = ["some-registry-name"] ``` -The value may also be an array of strings which are registry names that are -allowed to be published to. - +To prevent a package from being published to a registry (like crates.io) by mistake, +for instance to keep a package private in a company, +you can omit the [`version`](#the-version-field) field. +If you'd like to be more explicit, you can disable publishing: ```toml [package] # ... -publish = ["some-registry-name"] +publish = false ``` If publish array contains a single registry, `cargo publish` command will use @@ -511,6 +512,10 @@ package-name = "my-awesome-android-app" assets = "path/to/static" ``` +You'll need to look in the documentation for your tool to see how to use this field. +For Rust Projects that use `package.metadata` tables, see: +- [docs.rs](https://docs.rs/about/metadata) + There is a similar table at the workspace level at [`workspace.metadata`][workspace-metadata]. While cargo does not specify a format for the content of either of these tables, it is suggested that @@ -628,9 +633,9 @@ more detail. [docs.rs]: https://docs.rs/ [publishing]: publishing.md [Rust Edition]: ../../edition-guide/index.html -[spdx-2.1-license-expressions]: https://spdx.org/spdx-specification-21-web-version#h.jxpfx0ykyb60 -[spdx-license-list-3.11]: https://github.com/spdx/license-list-data/tree/v3.11 -[SPDX site]: https://spdx.org/license-list +[spdx-2.3-license-expressions]: https://spdx.github.io/spdx-spec/v2.3/SPDX-license-expressions/ +[spdx-license-list-3.20]: https://github.com/spdx/license-list-data/tree/v3.20 +[SPDX site]: https://spdx.org [TOML]: https://toml.io/ <script> diff --git a/src/tools/cargo/src/doc/src/reference/profiles.md b/src/tools/cargo/src/doc/src/reference/profiles.md index 15ca8953c..165b41d60 100644 --- a/src/tools/cargo/src/doc/src/reference/profiles.md +++ b/src/tools/cargo/src/doc/src/reference/profiles.md @@ -270,7 +270,7 @@ The default settings for the `dev` profile are: opt-level = 0 debug = true split-debuginfo = '...' # Platform-specific. -strip = false +strip = "none" debug-assertions = true overflow-checks = true lto = false @@ -293,7 +293,7 @@ The default settings for the `release` profile are: opt-level = 3 debug = false split-debuginfo = '...' # Platform-specific. -strip = false +strip = "none" debug-assertions = false overflow-checks = false lto = false diff --git a/src/tools/cargo/src/doc/src/reference/publishing.md b/src/tools/cargo/src/doc/src/reference/publishing.md index 54a8635fb..5dcb73d0f 100644 --- a/src/tools/cargo/src/doc/src/reference/publishing.md +++ b/src/tools/cargo/src/doc/src/reference/publishing.md @@ -139,7 +139,7 @@ Then run [`cargo publish`] as described above to upload the new version. > **Recommendation:** Consider the full release process and automate what you can. > > Each version should include: -> - A changelog entry, preferrably [manually curated](https://keepachangelog.com/en/1.0.0/) though a generated one is better than nothing +> - A changelog entry, preferably [manually curated](https://keepachangelog.com/en/1.0.0/) though a generated one is better than nothing > - A [git tag](https://git-scm.com/book/en/v2/Git-Basics-Tagging) pointing to the published commit > > Examples of third-party tools that are representative of different workflows include (in alphabetical order): diff --git a/src/tools/cargo/src/doc/src/reference/registry-authentication.md b/src/tools/cargo/src/doc/src/reference/registry-authentication.md index 9f8f7e979..f07bf7066 100644 --- a/src/tools/cargo/src/doc/src/reference/registry-authentication.md +++ b/src/tools/cargo/src/doc/src/reference/registry-authentication.md @@ -103,7 +103,7 @@ Install the provider with `cargo install cargo-credential-1password` In the config, add to (or create) `registry.global-credential-providers`: ```toml [registry] -global-credential-providers = ["cargo:token", "cargo-credential-1password --email you@example.com"] +global-credential-providers = ["cargo:token", "cargo-credential-1password --account my.1password.com"] ``` The values in `global-credential-providers` are split on spaces into path and command-line arguments. To diff --git a/src/tools/cargo/src/doc/src/reference/resolver.md b/src/tools/cargo/src/doc/src/reference/resolver.md index 7d01fb167..de9d21fbd 100644 --- a/src/tools/cargo/src/doc/src/reference/resolver.md +++ b/src/tools/cargo/src/doc/src/reference/resolver.md @@ -489,9 +489,43 @@ break the build. The following illustrates some problems you may experience, and some possible solutions. +### Why was a dependency included? + +Say you see dependency `rand` in the `cargo check` output but don't think its needed and want to understand why its being pulled in. + +You can run +```console +$ cargo tree --workspace --target all --all-features --invert rand +rand v0.8.5 +└── ... + +rand v0.8.5 +└── ... +``` + +You might identify that it was an activated feature that caused `rand` to show up. To figure out which package activated the feature, you can add the `--edges features` +```console +$ cargo tree --workspace --target all --all-features --edges features --invert rand +rand v0.8.5 +└── ... + +rand v0.8.5 +└── ... +``` + ### Unexpected dependency duplication -The resolver algorithm may converge on a solution that includes two copies of a +You see multiple instances of `rand` when you run +```console +$ cargo tree --workspace --target all --all-features --duplicates +rand v0.7.3 +└── ... + +rand v0.8.5 +└── ... +``` + +The resolver algorithm has converged on a solution that includes two copies of a dependency when one would suffice. For example: ```toml @@ -517,6 +551,17 @@ But, if you run into this situation, the [`cargo update`] command with the [`cargo update`]: ../commands/cargo-update.md +### Why wasn't a newer version selected? + +Say you noticed that the latest version of a dependency wasn't selected when you ran: +```console +$ cargo update +``` +You can enable some extra logging to see why this happened: +```console +$ env CARGO_LOG=cargo::core::resolver=trace cargo update +``` +**Note:** Cargo log targets and levels may change over time. ### SemVer-breaking patch release breaks the build diff --git a/src/tools/cargo/src/doc/src/reference/specifying-dependencies.md b/src/tools/cargo/src/doc/src/reference/specifying-dependencies.md index 746b5fcb2..2bdbbceee 100644 --- a/src/tools/cargo/src/doc/src/reference/specifying-dependencies.md +++ b/src/tools/cargo/src/doc/src/reference/specifying-dependencies.md @@ -135,7 +135,7 @@ separated with a comma, e.g., `>= 1.2, < 1.5`. > Avoid constraining the upper bound of a version to be anything less than the > next semver incompatible version > (e.g. avoid `">=2.0, <2.4"`) as other packages in the dependency tree may -> require a newer version, leading to an unresolvable error (see #6584). +> require a newer version, leading to an unresolvable error (see [#9029]). > Consider whether controlling the version in your [`Cargo.lock`] would be more > appropriate. > @@ -152,7 +152,7 @@ separated with a comma, e.g., `>= 1.2, < 1.5`. [`Cargo.lock`]: ../guide/cargo-toml-vs-cargo-lock.md [#2222]: https://github.com/rust-lang/cargo/issues/2222 -[#6584]: https://github.com/rust-lang/cargo/issues/6584 +[#9029]: https://github.com/rust-lang/cargo/issues/9029 [#10599]: https://github.com/rust-lang/cargo/issues/10599 ## Specifying dependencies from other registries diff --git a/src/tools/cargo/src/doc/src/reference/unstable.md b/src/tools/cargo/src/doc/src/reference/unstable.md index c8047de90..0683daa3c 100644 --- a/src/tools/cargo/src/doc/src/reference/unstable.md +++ b/src/tools/cargo/src/doc/src/reference/unstable.md @@ -93,6 +93,8 @@ For the latest nightly, see the [nightly version] of this page. * [codegen-backend](#codegen-backend) --- Select the codegen backend used by rustc. * [per-package-target](#per-package-target) --- Sets the `--target` to use for each individual package. * [artifact dependencies](#artifact-dependencies) --- Allow build artifacts to be included into other build artifacts and build them for different targets. + * [Edition 2024](#edition-2024) — Adds support for the 2024 Edition. + * [Profile `trim-paths` option](#profile-trim-paths-option) --- Control the sanitization of file paths in build outputs. * Information and metadata * [Build-plan](#build-plan) --- Emits JSON information on which commands will be run. * [unit-graph](#unit-graph) --- Emits JSON for Cargo's internal graph structure. @@ -1079,34 +1081,14 @@ you are ok with dev-deps being build for `cargo doc`. * RFC: [#3013](https://github.com/rust-lang/rfcs/pull/3013) * Tracking Issue: [#10554](https://github.com/rust-lang/cargo/issues/10554) -`-Z check-cfg` command line enables compile time checking of name and values in `#[cfg]`, `cfg!`, -`#[link]` and `#[cfg_attr]` with the `rustc` and `rustdoc` unstable `--check-cfg` command line. +`-Z check-cfg` command line enables compile time checking of Cargo features as well as `rustc` +well known names and values in `#[cfg]`, `cfg!`, `#[link]` and `#[cfg_attr]` with the `rustc` +and `rustdoc` unstable `--check-cfg` command line. -It's values are: - - `features`: enables features checking via `--check-cfg=values(feature, ...)`. - Note than this command line options will probably become the default when stabilizing. - - `names`: enables well known names checking via `--check-cfg=names()`. - - `values`: enables well known values checking via `--check-cfg=values()`. - - `output`: enable the use of `rustc-check-cfg` in build script. +You can use the flag like this: -For instance: - -``` -cargo check -Z unstable-options -Z check-cfg=features -cargo check -Z unstable-options -Z check-cfg=names -cargo check -Z unstable-options -Z check-cfg=values -cargo check -Z unstable-options -Z check-cfg=features,names,values ``` - -Or for `output`: - -```rust,no_run -// build.rs -println!("cargo:rustc-check-cfg=names(foo, bar)"); -``` - -``` -cargo check -Z unstable-options -Z check-cfg=output +cargo check -Z unstable-options -Z check-cfg ``` ### `cargo:rustc-check-cfg=CHECK_CFG` @@ -1115,12 +1097,23 @@ 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 detection of unexpected conditional compilation name and/or values. -This can only be used in combination with `-Zcheck-cfg=output` otherwise it is ignored +This can only be used in combination with `-Zcheck-cfg` otherwise it is ignored with a warning. -If you want to integrate with Cargo features, use `-Zcheck-cfg=features` instead of +If you want to integrate with Cargo features, only use `-Zcheck-cfg` instead of trying to do it manually with this option. +You can use the instruction like this: + +```rust,no_run +// build.rs +println!("cargo:rustc-check-cfg=cfg(foo, bar)"); +``` + +``` +cargo check -Z unstable-options -Z check-cfg +``` + ## codegen-backend The `codegen-backend` feature makes it possible to select the codegen backend used by rustc using a profile. @@ -1229,9 +1222,6 @@ at the start of the infostring at the top of the file. Inferred / defaulted manifest fields: - `package.name = <slugified file stem>` -- `package.version = "0.0.0"` to [call attention to this crate being used in unexpected places](https://matklad.github.io/2021/08/22/large-rust-workspaces.html#Smaller-Tips) -- `package.publish = false` to avoid accidental publishes, particularly if we - later add support for including them in a workspace. - `package.edition = <current>` to avoid always having to add an embedded manifest at the cost of potentially breaking scripts on rust upgrades - Warn when `edition` is unspecified to raise awareness of this @@ -1271,6 +1261,128 @@ Differences between `cargo run --manifest-path <path>` and `cargo <path>` ### Documentation Updates +## Edition 2024 +* Tracking Issue: (none created yet) +* RFC: [rust-lang/rfcs#3501](https://github.com/rust-lang/rfcs/pull/3501) + +Support for the 2024 [edition] can be enabled by adding the `edition2024` +unstable feature to the top of `Cargo.toml`: + +```toml +cargo-features = ["edition2024"] + +[package] +name = "my-package" +version = "0.1.0" +edition = "2024" +``` + +If you want to transition an existing project from a previous edition, then +`cargo fix --edition` can be used on the nightly channel. After running `cargo +fix`, you can switch the edition to 2024 as illustrated above. + +This feature is very unstable, and is only intended for early testing and +experimentation. Future nightly releases may introduce changes for the 2024 +edition that may break your build. + +[edition]: ../../edition-guide/index.html + +## Profile `trim-paths` option + +* Tracking Issue: [rust-lang/cargo#12137](https://github.com/rust-lang/cargo/issues/12137) +* Tracking Rustc Issue: [rust-lang/rust#111540](https://github.com/rust-lang/rust/issues/111540) + +This adds a new profile setting to control how paths are sanitized in the resulting binary. +This can be enabled like so: + +```toml +cargo-features = ["trim-paths"] + +[package] +# ... + +[profile.release] +trim-paths = ["diagnostics", "object"] +``` + +To set this in a profile in Cargo configuration, +you need to use either `-Z trim-paths` or `[unstable]` table to enable it. +For example, + +```toml +# .cargo/config.toml +[unstable] +trim-paths = true + +[profile.release] +trim-paths = ["diagnostics", "object"] +``` + +### Documentation updates + +#### trim-paths + +*as a new ["Profiles settings" entry](./profiles.html#profile-settings)* + +`trim-paths` is a profile setting which enables and controls the sanitization of file paths in build outputs. +It takes the following values: + +- `"none"` and `false` --- disable path sanitization +- `"macro"` --- sanitize paths in the expansion of `std::file!()` macro. + This is where paths in embedded panic messages come from +- `"diagnostics"` --- sanitize paths in printed compiler diagnostics +- `"object"` --- sanitize paths in compiled executables or libraries +- `"all"` and `true` --- sanitize paths in all possible locations + +It also takes an array with the combinations of `"macro"`, `"diagnostics"`, and `"object"`. + +It is defaulted to `none` for the `dev` profile, and `object` for the `release` profile. +You can manually override it by specifying this option in `Cargo.toml`: + +```toml +[profile.dev] +trim-paths = "all" + +[profile.release] +trim-paths = ["object", "diagnostics"] +``` + +The default `release` profile setting (`object`) sanitizes only the paths in emitted executable or library files. +It always affects paths from macros such as panic messages, and in debug information only if they will be embedded together with the binary +(the default on platforms with ELF binaries, such as Linux and windows-gnu), +but will not touch them if they are in separate files (the default on Windows MSVC and macOS). +But the paths to these separate files are sanitized. + +If `trim-paths` is not `none` or `false`, then the following paths are sanitized if they appear in a selected scope: + +1. Path to the source files of the standard and core library (sysroot) will begin with `/rustc/[rustc commit hash]`, + e.g. `/home/username/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/result.rs` -> + `/rustc/fe72845f7bb6a77b9e671e6a4f32fe714962cec4/library/core/src/result.rs` +2. Path to the current package will be stripped, relatively to the current workspace root, e.g. `/home/username/crate/src/lib.rs` -> `src/lib.rs`. +3. Path to dependency packages will be replaced with `[package name]-[version]`. E.g. `/home/username/deps/foo/src/lib.rs` -> `foo-0.1.0/src/lib.rs` + +When a path to the source files of the standard and core library is *not* in scope for sanitization, +the emitted path will depend on if `rust-src` component is present. +If it is, then some paths will point to the copy of the source files on your file system; +if it isn't, then they will show up as `/rustc/[rustc commit hash]/library/...` +(just like when it is selected for sanitization). +Paths to all other source files will not be affected. + +This will not affect any hard-coded paths in the source code, such as in strings. + +#### Environment variable + +*as a new entry of ["Environment variables Cargo sets for build scripts"](./environment-variables.md#environment-variables-cargo-sets-for-crates)* + +* `CARGO_TRIM_PATHS` --- The value of `trim-paths` profile option. + `false`, `"none"`, and empty arrays would be converted to `none`. + `true` and `"all"` become `all`. + Values in a non-empty array would be joined into a comma-separated list. + If the build script introduces absolute paths to built artifacts (such as by invoking a compiler), + the user may request them to be sanitized in different types of artifacts. + Common paths requiring sanitization include `OUT_DIR` and `CARGO_MANIFEST_DIR`, + plus any other introduced by the build script, such as include directories. + # Stabilized and removed features ## Compile progress diff --git a/src/tools/cargo/src/etc/man/cargo-add.1 b/src/tools/cargo/src/etc/man/cargo-add.1 index f69e6d0db..4e8b68566 100644 --- a/src/tools/cargo/src/etc/man/cargo-add.1 +++ b/src/tools/cargo/src/etc/man/cargo-add.1 @@ -44,7 +44,7 @@ If no source is specified, then a best effort will be made to select one, includ .sp When you add a package that is already present, the existing entry will be updated with the flags specified. .sp -Upon successful invocation, the enabled (\fB+\fR) and disabled (\fB\-\fR) \fIfeatures\fR <https://doc.rust\-lang.org/cargo/reference/features.md> of the specified +Upon successful invocation, the enabled (\fB+\fR) and disabled (\fB\-\fR) \fIfeatures\fR <https://doc.rust\-lang.org/cargo/reference/features.html> of the specified dependency will be listed in the command\[cq]s output. .SH "OPTIONS" .SS "Source options" diff --git a/src/tools/cargo/src/etc/man/cargo-bench.1 b/src/tools/cargo/src/etc/man/cargo-bench.1 index 64498c4d6..0f5a993e0 100644 --- a/src/tools/cargo/src/etc/man/cargo-bench.1 +++ b/src/tools/cargo/src/etc/man/cargo-bench.1 @@ -32,7 +32,7 @@ Benchmarks are built with the \fB\-\-test\fR option to \fBrustc\fR which creates special executable by linking your code with libtest. The executable automatically runs all functions annotated with the \fB#[bench]\fR attribute. Cargo passes the \fB\-\-bench\fR flag to the test harness to tell it to run -only benchmarks. +only benchmarks, regardless of whether the harness is libtest or a custom harness. .sp The libtest harness may be disabled by setting \fBharness = false\fR in the target manifest settings, in which case your code will need to provide its own \fBmain\fR @@ -282,7 +282,7 @@ target artifacts are placed in a separate directory. See the \fB\-\-profile\fR \fIname\fR .RS 4 Benchmark with the given profile. -See the \fIthe reference\fR <https://doc.rust\-lang.org/cargo/reference/profiles.html> for more details on profiles. +See \fIthe reference\fR <https://doc.rust\-lang.org/cargo/reference/profiles.html> for more details on profiles. .RE .sp \fB\-\-ignore\-rust\-version\fR diff --git a/src/tools/cargo/src/etc/man/cargo-build.1 b/src/tools/cargo/src/etc/man/cargo-build.1 index 6bcfe8093..4194d7b77 100644 --- a/src/tools/cargo/src/etc/man/cargo-build.1 +++ b/src/tools/cargo/src/etc/man/cargo-build.1 @@ -188,7 +188,7 @@ See also the \fB\-\-profile\fR option for choosing a specific profile by name. \fB\-\-profile\fR \fIname\fR .RS 4 Build with the given profile. -See the \fIthe reference\fR <https://doc.rust\-lang.org/cargo/reference/profiles.html> for more details on profiles. +See \fIthe reference\fR <https://doc.rust\-lang.org/cargo/reference/profiles.html> for more details on profiles. .RE .sp \fB\-\-ignore\-rust\-version\fR diff --git a/src/tools/cargo/src/etc/man/cargo-check.1 b/src/tools/cargo/src/etc/man/cargo-check.1 index fdbb84647..43d570058 100644 --- a/src/tools/cargo/src/etc/man/cargo-check.1 +++ b/src/tools/cargo/src/etc/man/cargo-check.1 @@ -190,7 +190,7 @@ test mode which will enable checking tests and enable the \fBtest\fR cfg option. See \fIrustc tests\fR <https://doc.rust\-lang.org/rustc/tests/index.html> for more detail. .sp -See the \fIthe reference\fR <https://doc.rust\-lang.org/cargo/reference/profiles.html> for more details on profiles. +See \fIthe reference\fR <https://doc.rust\-lang.org/cargo/reference/profiles.html> for more details on profiles. .RE .sp \fB\-\-ignore\-rust\-version\fR diff --git a/src/tools/cargo/src/etc/man/cargo-doc.1 b/src/tools/cargo/src/etc/man/cargo-doc.1 index 2bdd8867b..69f781de6 100644 --- a/src/tools/cargo/src/etc/man/cargo-doc.1 +++ b/src/tools/cargo/src/etc/man/cargo-doc.1 @@ -157,7 +157,7 @@ See also the \fB\-\-profile\fR option for choosing a specific profile by name. \fB\-\-profile\fR \fIname\fR .RS 4 Document with the given profile. -See the \fIthe reference\fR <https://doc.rust\-lang.org/cargo/reference/profiles.html> for more details on profiles. +See \fIthe reference\fR <https://doc.rust\-lang.org/cargo/reference/profiles.html> for more details on profiles. .RE .sp \fB\-\-ignore\-rust\-version\fR diff --git a/src/tools/cargo/src/etc/man/cargo-fix.1 b/src/tools/cargo/src/etc/man/cargo-fix.1 index 61083f214..1f10f179b 100644 --- a/src/tools/cargo/src/etc/man/cargo-fix.1 +++ b/src/tools/cargo/src/etc/man/cargo-fix.1 @@ -285,7 +285,7 @@ test mode which will enable checking tests and enable the \fBtest\fR cfg option. See \fIrustc tests\fR <https://doc.rust\-lang.org/rustc/tests/index.html> for more detail. .sp -See the \fIthe reference\fR <https://doc.rust\-lang.org/cargo/reference/profiles.html> for more details on profiles. +See \fIthe reference\fR <https://doc.rust\-lang.org/cargo/reference/profiles.html> for more details on profiles. .RE .sp \fB\-\-ignore\-rust\-version\fR diff --git a/src/tools/cargo/src/etc/man/cargo-init.1 b/src/tools/cargo/src/etc/man/cargo-init.1 index 56d1aca9f..b37622b52 100644 --- a/src/tools/cargo/src/etc/man/cargo-init.1 +++ b/src/tools/cargo/src/etc/man/cargo-init.1 @@ -37,7 +37,7 @@ Create a package with a library target (\fBsrc/lib.rs\fR). \fB\-\-edition\fR \fIedition\fR .RS 4 Specify the Rust edition to use. Default is 2021. -Possible values: 2015, 2018, 2021 +Possible values: 2015, 2018, 2021, 2024 .RE .sp \fB\-\-name\fR \fIname\fR diff --git a/src/tools/cargo/src/etc/man/cargo-install.1 b/src/tools/cargo/src/etc/man/cargo-install.1 index 5ca3180fd..d4d4d3eb6 100644 --- a/src/tools/cargo/src/etc/man/cargo-install.1 +++ b/src/tools/cargo/src/etc/man/cargo-install.1 @@ -114,7 +114,7 @@ will be used, beginning discovery at \fB$PATH/.cargo/config.toml\fR\&. \fB\-\-version\fR \fIversion\fR .RS 4 Specify a version to install. This may be a \fIversion -requirement\fR <https://doc.rust\-lang.org/cargo/reference/specifying\-dependencies.md>, like \fB~1.2\fR, to have Cargo +requirement\fR <https://doc.rust\-lang.org/cargo/reference/specifying\-dependencies.html>, like \fB~1.2\fR, to have Cargo select the newest version from the given requirement. If the version does not have a requirement operator (such as \fB^\fR or \fB~\fR), then it must be in the form \fIMAJOR.MINOR.PATCH\fR, and will install exactly that version; it is \fInot\fR @@ -270,7 +270,7 @@ See also the \fB\-\-profile\fR option for choosing a specific profile by name. \fB\-\-profile\fR \fIname\fR .RS 4 Install with the given profile. -See the \fIthe reference\fR <https://doc.rust\-lang.org/cargo/reference/profiles.html> for more details on profiles. +See \fIthe reference\fR <https://doc.rust\-lang.org/cargo/reference/profiles.html> for more details on profiles. .RE .sp \fB\-\-ignore\-rust\-version\fR diff --git a/src/tools/cargo/src/etc/man/cargo-login.1 b/src/tools/cargo/src/etc/man/cargo-login.1 index 92b3b3f3c..16f5773fa 100644 --- a/src/tools/cargo/src/etc/man/cargo-login.1 +++ b/src/tools/cargo/src/etc/man/cargo-login.1 @@ -6,12 +6,14 @@ .SH "NAME" cargo\-login \[em] Log in to a registry .SH "SYNOPSIS" -\fBcargo login\fR [\fIoptions\fR] [\fItoken\fR] \[en] [\fIargs\fR] +\fBcargo login\fR [\fIoptions\fR] [\fItoken\fR] [\fB\-\-\fR \fIargs\fR] .SH "DESCRIPTION" This command will run a credential provider to save a token so that commands that require authentication, such as \fBcargo\-publish\fR(1), will be automatically authenticated. .sp +All the arguments following the two dashes (\fB\-\-\fR) are passed to the credential provider. +.sp For the default \fBcargo:token\fR credential provider, the token is saved in \fB$CARGO_HOME/credentials.toml\fR\&. \fBCARGO_HOME\fR defaults to \fB\&.cargo\fR in your home directory. diff --git a/src/tools/cargo/src/etc/man/cargo-new.1 b/src/tools/cargo/src/etc/man/cargo-new.1 index 62e0eb157..f1939a543 100644 --- a/src/tools/cargo/src/etc/man/cargo-new.1 +++ b/src/tools/cargo/src/etc/man/cargo-new.1 @@ -32,7 +32,7 @@ Create a package with a library target (\fBsrc/lib.rs\fR). \fB\-\-edition\fR \fIedition\fR .RS 4 Specify the Rust edition to use. Default is 2021. -Possible values: 2015, 2018, 2021 +Possible values: 2015, 2018, 2021, 2024 .RE .sp \fB\-\-name\fR \fIname\fR diff --git a/src/tools/cargo/src/etc/man/cargo-run.1 b/src/tools/cargo/src/etc/man/cargo-run.1 index 293814674..2ecbbcf65 100644 --- a/src/tools/cargo/src/etc/man/cargo-run.1 +++ b/src/tools/cargo/src/etc/man/cargo-run.1 @@ -94,7 +94,7 @@ See also the \fB\-\-profile\fR option for choosing a specific profile by name. \fB\-\-profile\fR \fIname\fR .RS 4 Run with the given profile. -See the \fIthe reference\fR <https://doc.rust\-lang.org/cargo/reference/profiles.html> for more details on profiles. +See \fIthe reference\fR <https://doc.rust\-lang.org/cargo/reference/profiles.html> for more details on profiles. .RE .sp \fB\-\-ignore\-rust\-version\fR diff --git a/src/tools/cargo/src/etc/man/cargo-rustc.1 b/src/tools/cargo/src/etc/man/cargo-rustc.1 index 501e9208e..f3535b0f0 100644 --- a/src/tools/cargo/src/etc/man/cargo-rustc.1 +++ b/src/tools/cargo/src/etc/man/cargo-rustc.1 @@ -194,7 +194,7 @@ tests\fR <https://doc.rust\-lang.org/rustc/tests/index.html> for more detail. similar to the \fBtest\fR profile. .RE .sp -See the \fIthe reference\fR <https://doc.rust\-lang.org/cargo/reference/profiles.html> for more details on profiles. +See \fIthe reference\fR <https://doc.rust\-lang.org/cargo/reference/profiles.html> for more details on profiles. .RE .sp \fB\-\-ignore\-rust\-version\fR diff --git a/src/tools/cargo/src/etc/man/cargo-rustdoc.1 b/src/tools/cargo/src/etc/man/cargo-rustdoc.1 index 0335d6e54..72a182de8 100644 --- a/src/tools/cargo/src/etc/man/cargo-rustdoc.1 +++ b/src/tools/cargo/src/etc/man/cargo-rustdoc.1 @@ -176,7 +176,7 @@ See also the \fB\-\-profile\fR option for choosing a specific profile by name. \fB\-\-profile\fR \fIname\fR .RS 4 Document with the given profile. -See the \fIthe reference\fR <https://doc.rust\-lang.org/cargo/reference/profiles.html> for more details on profiles. +See \fIthe reference\fR <https://doc.rust\-lang.org/cargo/reference/profiles.html> for more details on profiles. .RE .sp \fB\-\-ignore\-rust\-version\fR diff --git a/src/tools/cargo/src/etc/man/cargo-test.1 b/src/tools/cargo/src/etc/man/cargo-test.1 index 8e460e167..467000147 100644 --- a/src/tools/cargo/src/etc/man/cargo-test.1 +++ b/src/tools/cargo/src/etc/man/cargo-test.1 @@ -309,7 +309,7 @@ See also the \fB\-\-profile\fR option for choosing a specific profile by name. \fB\-\-profile\fR \fIname\fR .RS 4 Test with the given profile. -See the \fIthe reference\fR <https://doc.rust\-lang.org/cargo/reference/profiles.html> for more details on profiles. +See \fIthe reference\fR <https://doc.rust\-lang.org/cargo/reference/profiles.html> for more details on profiles. .RE .sp \fB\-\-ignore\-rust\-version\fR diff --git a/src/tools/cargo/src/etc/man/cargo-vendor.1 b/src/tools/cargo/src/etc/man/cargo-vendor.1 index cb46f67cd..67744532e 100644 --- a/src/tools/cargo/src/etc/man/cargo-vendor.1 +++ b/src/tools/cargo/src/etc/man/cargo-vendor.1 @@ -14,8 +14,10 @@ the vendor directory specified by \fB<path>\fR will contain all remote sources f dependencies specified. Additional manifests beyond the default one can be specified with the \fB\-s\fR option. .sp -The \fBcargo vendor\fR command will also print out the configuration necessary -to use the vendored sources, which you will need to add to \fB\&.cargo/config.toml\fR\&. +The configuration necessary to use the vendored sources would be printed to +stdout after \fBcargo vendor\fR completes the vendoring process. +You will need to add or redirect it to your Cargo configuration file, +which is usually \fB\&.cargo/config.toml\fR locally for the current package. .SH "OPTIONS" .SS "Vendor Options" .sp @@ -205,5 +207,15 @@ cargo vendor \-s ../path/to/Cargo.toml .fi .RE .RE +.sp +.RS 4 +\h'-04' 4.\h'+01'Vendor and redirect the necessary vendor configs to a config file. +.sp +.RS 4 +.nf +cargo vendor > path/to/my/cargo/config.toml +.fi +.RE +.RE .SH "SEE ALSO" \fBcargo\fR(1) diff --git a/src/tools/cargo/tests/testsuite/artifact_dep.rs b/src/tools/cargo/tests/testsuite/artifact_dep.rs index 64aa9d8af..c51298735 100644 --- a/src/tools/cargo/tests/testsuite/artifact_dep.rs +++ b/src/tools/cargo/tests/testsuite/artifact_dep.rs @@ -2152,6 +2152,7 @@ fn doc_lib_true() { [DOCUMENTING] bar v0.0.1 ([CWD]/bar) [DOCUMENTING] foo v0.0.1 ([CWD]) [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/foo/index.html ", ) .run(); @@ -2227,6 +2228,7 @@ fn rustdoc_works_on_libs_with_artifacts_and_lib_false() { [COMPILING] bar v0.5.0 ([CWD]/bar) [DOCUMENTING] foo v0.0.1 ([CWD]) [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/foo/index.html ", ) .run(); diff --git a/src/tools/cargo/tests/testsuite/bad_config.rs b/src/tools/cargo/tests/testsuite/bad_config.rs index 82da880ea..1d3bb9ee1 100644 --- a/src/tools/cargo/tests/testsuite/bad_config.rs +++ b/src/tools/cargo/tests/testsuite/bad_config.rs @@ -373,7 +373,7 @@ Caused by: failed to clone into: [..] Caused by: - URLs need to specify the path to the repository + URL \"git://host.xz\" does not specify a path to a repository " } else { "\ @@ -1664,3 +1664,37 @@ note: Sources are not allowed to be defined multiple times. ) .run(); } + +#[cargo_test] +fn bad_trim_paths() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.0" + + [profile.dev] + trim-paths = "split-debuginfo" + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("check -Ztrim-paths") + .masquerade_as_nightly_cargo(&["trim-paths"]) + .with_status(101) + .with_stderr( + r#"error: failed to parse manifest at `[..]` + +Caused by: + TOML parse error at line 7, column 30 + | + 7 | trim-paths = "split-debuginfo" + | ^^^^^^^^^^^^^^^^^ + expected a boolean, "none", "diagnostics", "macro", "object", "all", or an array with these options +"#, + ) + .run(); +} diff --git a/src/tools/cargo/tests/testsuite/build.rs b/src/tools/cargo/tests/testsuite/build.rs index 1afa83918..23840ad9a 100644 --- a/src/tools/cargo/tests/testsuite/build.rs +++ b/src/tools/cargo/tests/testsuite/build.rs @@ -159,6 +159,29 @@ For more information, try '--help'. } #[cargo_test] +fn cargo_compile_with_unsupported_short_config_flag() { + let p = project() + .file("Cargo.toml", &basic_bin_manifest("foo")) + .file("src/foo.rs", &main_file(r#""i am foo""#, &[])) + .build(); + + p.cargo("build -c net.git-fetch-with-cli=true") + .with_stderr( + "\ +error: unexpected argument '-c' found + + tip: a similar argument exists: '--config' + +Usage: cargo[EXE] build [OPTIONS] + +For more information, try '--help'. +", + ) + .with_status(1) + .run(); +} + +#[cargo_test] fn cargo_compile_with_workspace_excluded() { let p = project().file("src/main.rs", "fn main() {}").build(); @@ -183,6 +206,30 @@ fn cargo_compile_manifest_path() { } #[cargo_test] +fn cargo_compile_with_wrong_manifest_path_flag() { + let p = project() + .file("Cargo.toml", &basic_bin_manifest("foo")) + .file("src/foo.rs", &main_file(r#""i am foo""#, &[])) + .build(); + + p.cargo("build --path foo/Cargo.toml") + .cwd(p.root().parent().unwrap()) + .with_stderr( + "\ +error: unexpected argument '--path' found + + tip: a similar argument exists: '--manifest-path' + +Usage: cargo[EXE] build [OPTIONS] + +For more information, try '--help'. +", + ) + .with_status(1) + .run(); +} + +#[cargo_test] fn chdir_gated() { let p = project() .file("Cargo.toml", &basic_bin_manifest("foo")) @@ -222,6 +269,33 @@ fn cargo_compile_directory_not_cwd() { } #[cargo_test] +fn cargo_compile_with_unsupported_short_unstable_feature_flag() { + let p = project() + .file("Cargo.toml", &basic_bin_manifest("foo")) + .file("src/foo.rs", &main_file(r#""i am foo""#, &[])) + .file(".cargo/config.toml", &"") + .build(); + + p.cargo("-zunstable-options -C foo build") + .masquerade_as_nightly_cargo(&["chdir"]) + .cwd(p.root().parent().unwrap()) + .with_stderr( + "\ +error: unexpected argument '-z' found + + tip: a similar argument exists: '-Z' + +Usage: cargo [..][OPTIONS] [COMMAND] + cargo [..][OPTIONS] -Zscript <MANIFEST_RS> [ARGS]... + +For more information, try '--help'. +", + ) + .with_status(1) + .run(); +} + +#[cargo_test] fn cargo_compile_directory_not_cwd_with_invalid_config() { let p = project() .file("Cargo.toml", &basic_bin_manifest("foo")) @@ -465,7 +539,7 @@ fn cargo_compile_with_forbidden_bin_target_name() { [ERROR] failed to parse manifest at `[..]` Caused by: - the binary target name `build` is forbidden, it conflicts with with cargo's build directory names + the binary target name `build` is forbidden, it conflicts with cargo's build directory names ", ) .run(); @@ -4190,6 +4264,30 @@ fn cargo_build_empty_target() { } #[cargo_test] +fn cargo_build_with_unsupported_short_target_flag() { + let p = project() + .file("Cargo.toml", &basic_bin_manifest("foo")) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("build -t") + .arg("") + .with_stderr( + "\ +error: unexpected argument '-t' found + + tip: a similar argument exists: '--target' + +Usage: cargo[EXE] build [OPTIONS] + +For more information, try '--help'. +", + ) + .with_status(1) + .run(); +} + +#[cargo_test] fn build_all_workspace() { let p = project() .file( @@ -4255,6 +4353,43 @@ fn build_all_exclude() { } #[cargo_test] +fn cargo_build_with_unsupported_short_exclude_flag() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [workspace] + members = ["bar", "baz"] + "#, + ) + .file("src/main.rs", "fn main() {}") + .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) + .file("bar/src/lib.rs", "pub fn bar() {}") + .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.0")) + .file("baz/src/lib.rs", "pub fn baz() { break_the_build(); }") + .build(); + + p.cargo("build --workspace -x baz") + .with_stderr( + "\ +error: unexpected argument '-x' found + + tip: a similar argument exists: '--exclude' + +Usage: cargo[EXE] build [OPTIONS] + +For more information, try '--help'. +", + ) + .with_status(1) + .run(); +} + +#[cargo_test] fn build_all_exclude_not_found() { let p = project() .file( diff --git a/src/tools/cargo/tests/testsuite/build_script.rs b/src/tools/cargo/tests/testsuite/build_script.rs index 0ccbb4e27..408ce6457 100644 --- a/src/tools/cargo/tests/testsuite/build_script.rs +++ b/src/tools/cargo/tests/testsuite/build_script.rs @@ -1471,6 +1471,7 @@ fn testing_and_such() { [DOCUMENTING] foo v0.5.0 ([CWD]) [RUNNING] `rustdoc [..]` [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/foo/index.html ", ) .run(); @@ -3778,8 +3779,8 @@ fn warnings_emitted() { [COMPILING] foo v0.5.0 ([..]) [RUNNING] `rustc [..]` [RUNNING] `[..]` -warning: foo -warning: bar +warning: foo@0.5.0: foo +warning: foo@0.5.0: bar [RUNNING] `rustc [..]` [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] ", @@ -3816,7 +3817,7 @@ fn warnings_emitted_when_build_script_panics() { p.cargo("build") .with_status(101) .with_stdout("") - .with_stderr_contains("warning: foo\nwarning: bar") + .with_stderr_contains("warning: foo@0.5.0: foo\nwarning: foo@0.5.0: bar") .run(); } @@ -3929,8 +3930,8 @@ fn warnings_printed_on_vv() { [COMPILING] bar v0.1.0 [RUNNING] `[..] rustc [..]` [RUNNING] `[..]` -warning: foo -warning: bar +warning: bar@0.1.0: foo +warning: bar@0.1.0: bar [RUNNING] `[..] rustc [..]` [COMPILING] foo v0.5.0 ([..]) [RUNNING] `[..] rustc [..]` diff --git a/src/tools/cargo/tests/testsuite/build_script_env.rs b/src/tools/cargo/tests/testsuite/build_script_env.rs index df574600c..afa2925f1 100644 --- a/src/tools/cargo/tests/testsuite/build_script_env.rs +++ b/src/tools/cargo/tests/testsuite/build_script_env.rs @@ -137,12 +137,12 @@ fn rustc_bootstrap() { // NOTE: uses RUSTC_BOOTSTRAP so it will be propagated to rustc // (this matters when tests are being run with a beta or stable cargo) .env("RUSTC_BOOTSTRAP", "1") - .with_stderr_contains("warning: Cannot set `RUSTC_BOOTSTRAP=1` [..]") + .with_stderr_contains("warning: has-dashes@0.0.1: Cannot set `RUSTC_BOOTSTRAP=1` [..]") .run(); // RUSTC_BOOTSTRAP set to the name of the library should warn p.cargo("check") .env("RUSTC_BOOTSTRAP", "has_dashes") - .with_stderr_contains("warning: Cannot set `RUSTC_BOOTSTRAP=1` [..]") + .with_stderr_contains("warning: has-dashes@0.0.1: Cannot set `RUSTC_BOOTSTRAP=1` [..]") .run(); // RUSTC_BOOTSTRAP set to some random value should error p.cargo("check") @@ -169,7 +169,7 @@ fn rustc_bootstrap() { // NOTE: uses RUSTC_BOOTSTRAP so it will be propagated to rustc // (this matters when tests are being run with a beta or stable cargo) .env("RUSTC_BOOTSTRAP", "1") - .with_stderr_contains("warning: Cannot set `RUSTC_BOOTSTRAP=1` [..]") + .with_stderr_contains("warning: foo@0.0.1: Cannot set `RUSTC_BOOTSTRAP=1` [..]") .run(); // RUSTC_BOOTSTRAP conditionally set when there's no library should error (regardless of the value) p.cargo("check") @@ -181,6 +181,22 @@ fn rustc_bootstrap() { } #[cargo_test] +fn build_script_env_verbose() { + let build_rs = r#" + fn main() {} + "#; + let p = project() + .file("Cargo.toml", &basic_manifest("verbose-build", "0.0.1")) + .file("src/lib.rs", "") + .file("build.rs", build_rs) + .build(); + + p.cargo("check -vv") + .with_stderr_contains("[RUNNING] `[..]CARGO=[..]build-script-build`") + .run(); +} + +#[cargo_test] #[cfg(target_arch = "x86_64")] fn build_script_sees_cfg_target_feature() { let build_rs = r#" diff --git a/src/tools/cargo/tests/testsuite/build_script_extra_link_arg.rs b/src/tools/cargo/tests/testsuite/build_script_extra_link_arg.rs index ade262fec..964a55e97 100644 --- a/src/tools/cargo/tests/testsuite/build_script_extra_link_arg.rs +++ b/src/tools/cargo/tests/testsuite/build_script_extra_link_arg.rs @@ -204,7 +204,7 @@ fn cdylib_link_arg_transitive() { [COMPILING] bar v1.0.0 [..] [RUNNING] `rustc --crate-name build_script_build bar/build.rs [..] [RUNNING] `[..]build-script-build[..] -warning: cargo:rustc-link-arg-cdylib was specified in the build script of bar v1.0.0 \ +warning: bar@1.0.0: cargo:rustc-link-arg-cdylib was specified in the build script of bar v1.0.0 \ ([ROOT]/foo/bar), but that package does not contain a cdylib target Allowing this was an unintended change in the 1.50 release, and may become an error in \ diff --git a/src/tools/cargo/tests/testsuite/cache_lock.rs b/src/tools/cargo/tests/testsuite/cache_lock.rs new file mode 100644 index 000000000..f5b681f3a --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cache_lock.rs @@ -0,0 +1,304 @@ +//! Tests for `CacheLock`. + +use crate::config::ConfigBuilder; +use cargo::util::cache_lock::{CacheLockMode, CacheLocker}; +use cargo_test_support::paths::{self, CargoPathExt}; +use cargo_test_support::{retry, thread_wait_timeout, threaded_timeout}; +use std::thread::JoinHandle; + +/// Helper to verify that it is OK to acquire the given lock (it shouldn't block). +fn verify_lock_is_ok(mode: CacheLockMode) { + let root = paths::root(); + threaded_timeout(10, move || { + let config = ConfigBuilder::new().root(root).build(); + let locker = CacheLocker::new(); + // This would block if it is held. + let _lock = locker.lock(&config, mode).unwrap(); + assert!(locker.is_locked(mode)); + }); +} + +/// Helper to acquire two locks from the same locker. +fn a_b_nested(a: CacheLockMode, b: CacheLockMode) { + let config = ConfigBuilder::new().build(); + let locker = CacheLocker::new(); + let lock1 = locker.lock(&config, a).unwrap(); + assert!(locker.is_locked(a)); + let lock2 = locker.lock(&config, b).unwrap(); + assert!(locker.is_locked(b)); + drop(lock2); + drop(lock1); + // Verify locks were unlocked. + verify_lock_is_ok(CacheLockMode::Shared); + verify_lock_is_ok(CacheLockMode::DownloadExclusive); + verify_lock_is_ok(CacheLockMode::MutateExclusive); +} + +/// Helper to acquire two locks from separate lockers, verifying that they +/// don't block each other. +fn a_then_b_separate_not_blocked(a: CacheLockMode, b: CacheLockMode, verify: CacheLockMode) { + let config = ConfigBuilder::new().build(); + let locker1 = CacheLocker::new(); + let lock1 = locker1.lock(&config, a).unwrap(); + assert!(locker1.is_locked(a)); + let locker2 = CacheLocker::new(); + let lock2 = locker2.lock(&config, b).unwrap(); + assert!(locker2.is_locked(b)); + let thread = verify_lock_would_block(verify); + // Unblock the thread. + drop(lock1); + drop(lock2); + // Verify the thread is unblocked. + thread_wait_timeout::<()>(100, thread); +} + +/// Helper to acquire two locks from separate lockers, verifying that the +/// second one blocks. +fn a_then_b_separate_blocked(a: CacheLockMode, b: CacheLockMode) { + let config = ConfigBuilder::new().build(); + let locker = CacheLocker::new(); + let lock = locker.lock(&config, a).unwrap(); + assert!(locker.is_locked(a)); + let thread = verify_lock_would_block(b); + // Unblock the thread. + drop(lock); + // Verify the thread is unblocked. + thread_wait_timeout::<()>(100, thread); +} + +/// Helper to verify that acquiring the given mode would block. +/// +/// Always call `thread_wait_timeout` on the result. +#[must_use] +fn verify_lock_would_block(mode: CacheLockMode) -> JoinHandle<()> { + let root = paths::root(); + // Spawn a thread that will block on the lock. + let thread = std::thread::spawn(move || { + let config = ConfigBuilder::new().root(root).build(); + let locker2 = CacheLocker::new(); + let lock2 = locker2.lock(&config, mode).unwrap(); + assert!(locker2.is_locked(mode)); + drop(lock2); + }); + // Verify that it blocked. + retry(100, || { + if let Ok(s) = std::fs::read_to_string(paths::root().join("shell.out")) { + if s.trim().starts_with("Blocking waiting for file lock on") { + return Some(()); + } else { + eprintln!("unexpected output: {s}"); + // Try again, it might have been partially written. + } + } + None + }); + thread +} + +#[test] +fn new_is_unlocked() { + let locker = CacheLocker::new(); + assert!(!locker.is_locked(CacheLockMode::Shared)); + assert!(!locker.is_locked(CacheLockMode::DownloadExclusive)); + assert!(!locker.is_locked(CacheLockMode::MutateExclusive)); +} + +#[cargo_test] +fn multiple_shared() { + // Test that two nested shared locks from the same locker are safe to acquire. + a_b_nested(CacheLockMode::Shared, CacheLockMode::Shared); +} + +#[cargo_test] +fn multiple_shared_separate() { + // Test that two independent shared locks are safe to acquire at the same time. + a_then_b_separate_not_blocked( + CacheLockMode::Shared, + CacheLockMode::Shared, + CacheLockMode::MutateExclusive, + ); +} + +#[cargo_test] +fn multiple_download() { + // That that two nested download locks from the same locker are safe to acquire. + a_b_nested( + CacheLockMode::DownloadExclusive, + CacheLockMode::DownloadExclusive, + ); +} + +#[cargo_test] +fn multiple_mutate() { + // That that two nested mutate locks from the same locker are safe to acquire. + a_b_nested( + CacheLockMode::MutateExclusive, + CacheLockMode::MutateExclusive, + ); +} + +#[cargo_test] +#[should_panic(expected = "lock is not allowed")] +fn download_then_shared() { + // This sequence is not supported. + a_b_nested(CacheLockMode::DownloadExclusive, CacheLockMode::Shared); +} + +#[cargo_test] +#[should_panic(expected = "lock upgrade from shared to exclusive not supported")] +fn shared_then_mutate() { + // This sequence is not supported. + a_b_nested(CacheLockMode::Shared, CacheLockMode::MutateExclusive); +} + +#[cargo_test] +fn shared_then_download() { + a_b_nested(CacheLockMode::Shared, CacheLockMode::DownloadExclusive); + // Verify drop actually unlocked. + verify_lock_is_ok(CacheLockMode::DownloadExclusive); + verify_lock_is_ok(CacheLockMode::MutateExclusive); +} + +#[cargo_test] +fn mutate_then_shared() { + a_b_nested(CacheLockMode::MutateExclusive, CacheLockMode::Shared); + // Verify drop actually unlocked. + verify_lock_is_ok(CacheLockMode::MutateExclusive); +} + +#[cargo_test] +fn download_then_mutate() { + a_b_nested( + CacheLockMode::DownloadExclusive, + CacheLockMode::MutateExclusive, + ); + // Verify drop actually unlocked. + verify_lock_is_ok(CacheLockMode::DownloadExclusive); + verify_lock_is_ok(CacheLockMode::MutateExclusive); +} + +#[cargo_test] +fn mutate_then_download() { + a_b_nested( + CacheLockMode::MutateExclusive, + CacheLockMode::DownloadExclusive, + ); + // Verify drop actually unlocked. + verify_lock_is_ok(CacheLockMode::MutateExclusive); + verify_lock_is_ok(CacheLockMode::DownloadExclusive); +} + +#[cargo_test] +fn readonly() { + // In a permission denied situation, it should still allow a lock. It just + // silently behaves as-if it was locked. + let cargo_home = paths::home().join(".cargo"); + std::fs::create_dir_all(&cargo_home).unwrap(); + let mut perms = std::fs::metadata(&cargo_home).unwrap().permissions(); + perms.set_readonly(true); + std::fs::set_permissions(&cargo_home, perms).unwrap(); + let config = ConfigBuilder::new().build(); + let locker = CacheLocker::new(); + for mode in [ + CacheLockMode::Shared, + CacheLockMode::DownloadExclusive, + CacheLockMode::MutateExclusive, + ] { + let _lock1 = locker.lock(&config, mode).unwrap(); + // Make sure it can recursively acquire the lock, too. + let _lock2 = locker.lock(&config, mode).unwrap(); + } +} + +#[cargo_test] +fn download_then_shared_separate() { + a_then_b_separate_not_blocked( + CacheLockMode::DownloadExclusive, + CacheLockMode::Shared, + CacheLockMode::MutateExclusive, + ); +} + +#[cargo_test] +fn shared_then_download_separate() { + a_then_b_separate_not_blocked( + CacheLockMode::Shared, + CacheLockMode::DownloadExclusive, + CacheLockMode::MutateExclusive, + ); +} + +#[cargo_test] +fn multiple_download_separate() { + // Test that with two independent download locks, the second blocks until + // the first is released. + a_then_b_separate_blocked( + CacheLockMode::DownloadExclusive, + CacheLockMode::DownloadExclusive, + ); +} + +#[cargo_test] +fn multiple_mutate_separate() { + // Test that with two independent mutate locks, the second blocks until + // the first is released. + a_then_b_separate_blocked( + CacheLockMode::MutateExclusive, + CacheLockMode::MutateExclusive, + ); +} + +#[cargo_test] +fn shared_then_mutate_separate() { + a_then_b_separate_blocked(CacheLockMode::Shared, CacheLockMode::MutateExclusive); +} + +#[cargo_test] +fn download_then_mutate_separate() { + a_then_b_separate_blocked( + CacheLockMode::DownloadExclusive, + CacheLockMode::MutateExclusive, + ); +} + +#[cargo_test] +fn mutate_then_download_separate() { + a_then_b_separate_blocked( + CacheLockMode::MutateExclusive, + CacheLockMode::DownloadExclusive, + ); +} + +#[cargo_test] +fn mutate_then_shared_separate() { + a_then_b_separate_blocked(CacheLockMode::MutateExclusive, CacheLockMode::Shared); +} + +#[cargo_test(ignore_windows = "no method to prevent creating or locking a file")] +fn mutate_err_is_atomic() { + // Verifies that when getting a mutate lock, that if the first lock + // succeeds, but the second one fails, that the first lock is released. + let config = ConfigBuilder::new().build(); + let locker = CacheLocker::new(); + let cargo_home = config.home().as_path_unlocked(); + let cache_path = cargo_home.join(".package-cache"); + // This is a hacky way to force an error acquiring the download lock. By + // making it a directory, it is unable to open it. + // TODO: Unfortunately this doesn't work on Windows. I don't have any + // ideas on how to simulate an error on Windows. + cache_path.mkdir_p(); + match locker.lock(&config, CacheLockMode::MutateExclusive) { + Ok(_) => panic!("did not expect lock to succeed"), + Err(e) => { + let msg = format!("{e:?}"); + assert!(msg.contains("failed to open:"), "{msg}"); + } + } + assert!(!locker.is_locked(CacheLockMode::MutateExclusive)); + assert!(!locker.is_locked(CacheLockMode::DownloadExclusive)); + assert!(!locker.is_locked(CacheLockMode::Shared)); + cache_path.rm_rf(); + verify_lock_is_ok(CacheLockMode::DownloadExclusive); + verify_lock_is_ok(CacheLockMode::Shared); + verify_lock_is_ok(CacheLockMode::MutateExclusive); +} diff --git a/src/tools/cargo/tests/testsuite/cargo_add/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/mod.rs index 8c03b30dc..e8633b0c4 100644 --- a/src/tools/cargo/tests/testsuite/cargo_add/mod.rs +++ b/src/tools/cargo/tests/testsuite/cargo_add/mod.rs @@ -99,6 +99,7 @@ mod path_dev; mod path_inferred_name; mod path_inferred_name_conflicts_full_feature; mod path_normalized_name; +mod preserve_dep_std_table; mod preserve_features_table; mod preserve_sorted; mod preserve_unsorted; diff --git a/src/tools/cargo/tests/testsuite/cargo_add/preserve_dep_std_table/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/preserve_dep_std_table/in/Cargo.toml new file mode 100644 index 000000000..aa8584ff4 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_add/preserve_dep_std_table/in/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "xxx" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies.your-face] +# Leading version +version = "99999.0.0" # Trailing version +# Leading optional +optional = true # Trailing optional +# Leading features +features = [] # Trailing features diff --git a/src/tools/cargo/tests/testsuite/cargo_init/empty_dir/.keep b/src/tools/cargo/tests/testsuite/cargo_add/preserve_dep_std_table/in/src/lib.rs index e69de29bb..e69de29bb 100644 --- a/src/tools/cargo/tests/testsuite/cargo_init/empty_dir/.keep +++ b/src/tools/cargo/tests/testsuite/cargo_add/preserve_dep_std_table/in/src/lib.rs diff --git a/src/tools/cargo/tests/testsuite/cargo_add/preserve_dep_std_table/mod.rs b/src/tools/cargo/tests/testsuite/cargo_add/preserve_dep_std_table/mod.rs new file mode 100644 index 000000000..1998fa742 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_add/preserve_dep_std_table/mod.rs @@ -0,0 +1,31 @@ +use cargo_test_support::compare::assert_ui; +use cargo_test_support::prelude::*; +use cargo_test_support::Project; + +use cargo_test_support::curr_dir; + +#[cargo_test] +fn case() { + cargo_test_support::registry::init(); + cargo_test_support::registry::Package::new("your-face", "99999.0.0+my-package") + .feature("nose", &[]) + .feature("mouth", &[]) + .feature("eyes", &[]) + .feature("ears", &[]) + .publish(); + + 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("your-face --no-optional") + .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/src/tools/cargo/tests/testsuite/cargo_add/preserve_dep_std_table/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_add/preserve_dep_std_table/out/Cargo.toml new file mode 100644 index 000000000..76e50ce37 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_add/preserve_dep_std_table/out/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "xxx" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies.your-face] +# Leading version +version = "99999.0.0" # Trailing version +# Leading features +features = [] # Trailing features diff --git a/src/tools/cargo/tests/testsuite/cargo_add/preserve_dep_std_table/stderr.log b/src/tools/cargo/tests/testsuite/cargo_add/preserve_dep_std_table/stderr.log new file mode 100644 index 000000000..796b9601b --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_add/preserve_dep_std_table/stderr.log @@ -0,0 +1,7 @@ + Updating `dummy-registry` index + Adding your-face v99999.0.0 to dependencies. + Features: + - ears + - eyes + - mouth + - nose diff --git a/src/tools/cargo/tests/testsuite/cargo_add/preserve_dep_std_table/stdout.log b/src/tools/cargo/tests/testsuite/cargo_add/preserve_dep_std_table/stdout.log new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_add/preserve_dep_std_table/stdout.log diff --git a/src/tools/cargo/tests/testsuite/cargo_bench/help/stdout.log b/src/tools/cargo/tests/testsuite/cargo_bench/help/stdout.log index 430d8be42..95546b4a3 100644 --- a/src/tools/cargo/tests/testsuite/cargo_bench/help/stdout.log +++ b/src/tools/cargo/tests/testsuite/cargo_bench/help/stdout.log @@ -1,10 +1,10 @@ Execute all benchmarks of a local package -Usage: cargo[EXE] bench [OPTIONS] [BENCHNAME] [-- [args]...] +Usage: cargo[EXE] bench [OPTIONS] [BENCHNAME] [-- [ARGS]...] Arguments: [BENCHNAME] If specified, only run benches containing this string in their names - [args]... Arguments for the bench binary + [ARGS]... Arguments for the bench binary Options: --no-run Compile, but don't run benchmarks @@ -31,9 +31,9 @@ Target Selection: --bin [<NAME>] Benchmark only the specified binary --examples Benchmark all examples --example [<NAME>] Benchmark only the specified example - --tests Benchmark all tests + --tests Benchmark all test targets --test [<NAME>] Benchmark only the specified test target - --benches Benchmark all benches + --benches Benchmark all bench targets --bench [<NAME>] Benchmark only the specified bench target --all-targets Benchmark all targets diff --git a/src/tools/cargo/tests/testsuite/cargo_bench/no_keep_going/stderr.log b/src/tools/cargo/tests/testsuite/cargo_bench/no_keep_going/stderr.log index 7b94abbc4..daaa8f093 100644 --- a/src/tools/cargo/tests/testsuite/cargo_bench/no_keep_going/stderr.log +++ b/src/tools/cargo/tests/testsuite/cargo_bench/no_keep_going/stderr.log @@ -2,6 +2,6 @@ error: unexpected argument '--keep-going' found tip: use `--no-fail-fast` to run as many tests as possible regardless of failure -Usage: cargo[EXE] bench [OPTIONS] [BENCHNAME] [-- [args]...] +Usage: cargo[EXE] bench [OPTIONS] [BENCHNAME] [-- [ARGS]...] For more information, try '--help'. diff --git a/src/tools/cargo/tests/testsuite/cargo_build/help/stdout.log b/src/tools/cargo/tests/testsuite/cargo_build/help/stdout.log index 56b934cd1..58b12cdcd 100644 --- a/src/tools/cargo/tests/testsuite/cargo_build/help/stdout.log +++ b/src/tools/cargo/tests/testsuite/cargo_build/help/stdout.log @@ -26,9 +26,9 @@ Target Selection: --bin [<NAME>] Build only the specified binary --examples Build all examples --example [<NAME>] Build only the specified example - --tests Build all tests + --tests Build all test targets --test [<NAME>] Build only the specified test target - --benches Build all benches + --benches Build all bench targets --bench [<NAME>] Build only the specified bench target --all-targets Build all targets diff --git a/src/tools/cargo/tests/testsuite/cargo_check/help/stdout.log b/src/tools/cargo/tests/testsuite/cargo_check/help/stdout.log index 92d44a6de..bbf090d1d 100644 --- a/src/tools/cargo/tests/testsuite/cargo_check/help/stdout.log +++ b/src/tools/cargo/tests/testsuite/cargo_check/help/stdout.log @@ -26,9 +26,9 @@ Target Selection: --bin [<NAME>] Check only the specified binary --examples Check all examples --example [<NAME>] Check only the specified example - --tests Check all tests + --tests Check all test targets --test [<NAME>] Check only the specified test target - --benches Check all benches + --benches Check all bench targets --bench [<NAME>] Check only the specified bench target --all-targets Check all targets diff --git a/src/tools/cargo/tests/testsuite/cargo_command.rs b/src/tools/cargo/tests/testsuite/cargo_command.rs index 62869387f..80885805b 100644 --- a/src/tools/cargo/tests/testsuite/cargo_command.rs +++ b/src/tools/cargo/tests/testsuite/cargo_command.rs @@ -294,7 +294,9 @@ fn find_closest_dont_correct_nonsense() { "\ [ERROR] no such command: `there-is-no-way-that-there-is-a-command-close-to-this` -<tab>View all installed commands with `cargo --list`", +<tab>View all installed commands with `cargo --list` +<tab>Find a package to install `there-is-no-way-that-there-is-a-command-close-to-this` with `cargo search cargo-there-is-no-way-that-there-is-a-command-close-to-this` +", ) .run(); } @@ -307,7 +309,9 @@ fn displays_subcommand_on_error() { "\ [ERROR] no such command: `invalid-command` -<tab>View all installed commands with `cargo --list`", +<tab>View all installed commands with `cargo --list` +<tab>Find a package to install `invalid-command` with `cargo search cargo-invalid-command` +", ) .run(); } @@ -529,7 +533,9 @@ error: no such command: `bluid` <tab>Did you mean `build`? -<tab>View all installed commands with `cargo --list`", +<tab>View all installed commands with `cargo --list` +<tab>Find a package to install `bluid` with `cargo search cargo-bluid` +", ) .run(); } diff --git a/src/tools/cargo/tests/testsuite/cargo_features.rs b/src/tools/cargo/tests/testsuite/cargo_features.rs index cf7ef0190..d319ed686 100644 --- a/src/tools/cargo/tests/testsuite/cargo_features.rs +++ b/src/tools/cargo/tests/testsuite/cargo_features.rs @@ -594,7 +594,13 @@ fn z_flags_rejected() { p.cargo("check -Zarg") .masquerade_as_nightly_cargo(&["test-dummy-unstable"]) .with_status(101) - .with_stderr("error: unknown `-Z` flag specified: arg") + .with_stderr( + r#"error: unknown `-Z` flag specified: arg + +For available unstable features, see https://doc.rust-lang.org/nightly/cargo/reference/unstable.html +If you intended to use an unstable rustc feature, try setting `RUSTFLAGS="-Zarg"` +"#, + ) .run(); p.cargo("check -Zprint-im-a-teapot") diff --git a/src/tools/cargo/tests/testsuite/cargo_fix/help/stdout.log b/src/tools/cargo/tests/testsuite/cargo_fix/help/stdout.log index dbbd11b77..3e8b1427f 100644 --- a/src/tools/cargo/tests/testsuite/cargo_fix/help/stdout.log +++ b/src/tools/cargo/tests/testsuite/cargo_fix/help/stdout.log @@ -31,9 +31,9 @@ Target Selection: --bin [<NAME>] Fix only the specified binary --examples Fix all examples --example [<NAME>] Fix only the specified example - --tests Fix all tests + --tests Fix all test targets --test [<NAME>] Fix only the specified test target - --benches Fix all benches + --benches Fix all bench targets --bench [<NAME>] Fix only the specified bench target --all-targets Fix all targets (default) diff --git a/src/tools/cargo/tests/testsuite/cargo_init/help/stdout.log b/src/tools/cargo/tests/testsuite/cargo_init/help/stdout.log index 0eb4c976b..588b45ccf 100644 --- a/src/tools/cargo/tests/testsuite/cargo_init/help/stdout.log +++ b/src/tools/cargo/tests/testsuite/cargo_init/help/stdout.log @@ -1,9 +1,9 @@ Create a new cargo package in an existing directory -Usage: cargo[EXE] init [OPTIONS] [path] +Usage: cargo[EXE] init [OPTIONS] [PATH] Arguments: - [path] [default: .] + [PATH] [default: .] Options: --vcs <VCS> Initialize a new repository for the given version control system, @@ -12,7 +12,7 @@ Options: --bin Use a binary (application) template [default] --lib Use a library template --edition <YEAR> Edition to set for the crate generated [possible values: 2015, 2018, - 2021] + 2021, 2024] --name <NAME> Set the resulting package name, defaults to the directory name --registry <REGISTRY> Registry to use -q, --quiet Do not print cargo log messages diff --git a/src/tools/cargo/tests/testsuite/cargo_init/mod.rs b/src/tools/cargo/tests/testsuite/cargo_init/mod.rs index a1988a06a..0b397111e 100644 --- a/src/tools/cargo/tests/testsuite/cargo_init/mod.rs +++ b/src/tools/cargo/tests/testsuite/cargo_init/mod.rs @@ -42,3 +42,4 @@ mod simple_hg_ignore_exists; mod simple_lib; mod unknown_flags; mod with_argument; +mod workspace_add_member; diff --git a/src/tools/cargo/tests/testsuite/cargo_init/unknown_flags/stderr.log b/src/tools/cargo/tests/testsuite/cargo_init/unknown_flags/stderr.log index 980e8acd8..04a3c3ff0 100644 --- a/src/tools/cargo/tests/testsuite/cargo_init/unknown_flags/stderr.log +++ b/src/tools/cargo/tests/testsuite/cargo_init/unknown_flags/stderr.log @@ -2,6 +2,6 @@ error: unexpected argument '--flag' found tip: to pass '--flag' as a value, use '-- --flag' -Usage: cargo[EXE] init <path> +Usage: cargo[EXE] init <PATH> For more information, try '--help'. diff --git a/src/tools/cargo/tests/testsuite/cargo_init/workspace_add_member/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_init/workspace_add_member/in/Cargo.toml new file mode 100644 index 000000000..61bdb9cbf --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_init/workspace_add_member/in/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +resolver = "2" diff --git a/src/tools/cargo/tests/testsuite/cargo_init/workspace_add_member/in/crates/foo/.keep b/src/tools/cargo/tests/testsuite/cargo_init/workspace_add_member/in/crates/foo/.keep new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_init/workspace_add_member/in/crates/foo/.keep diff --git a/src/tools/cargo/tests/testsuite/cargo_init/workspace_add_member/mod.rs b/src/tools/cargo/tests/testsuite/cargo_init/workspace_add_member/mod.rs new file mode 100644 index 000000000..87e2af0e5 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_init/workspace_add_member/mod.rs @@ -0,0 +1,21 @@ +use cargo_test_support::compare::assert_ui; +use cargo_test_support::prelude::*; +use cargo_test_support::Project; + +use cargo_test_support::curr_dir; + +#[cargo_test] +fn case() { + let project = Project::from_template(curr_dir!().join("in")); + let project_root = &project.root(); + + snapbox::cmd::Command::cargo_ui() + .arg_line("init --bin --vcs none") + .current_dir(project_root.join("crates").join("foo")) + .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/src/tools/cargo/tests/testsuite/cargo_init/workspace_add_member/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_init/workspace_add_member/out/Cargo.toml new file mode 100644 index 000000000..18a4e7cf2 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_init/workspace_add_member/out/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +resolver = "2" +members = ["crates/foo"] diff --git a/src/tools/cargo/tests/testsuite/cargo_init/workspace_add_member/out/crates/foo/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_init/workspace_add_member/out/crates/foo/Cargo.toml new file mode 100644 index 000000000..1d9cfe317 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_init/workspace_add_member/out/crates/foo/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "foo" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/tools/cargo/tests/testsuite/cargo_init/workspace_add_member/out/crates/foo/src/main.rs b/src/tools/cargo/tests/testsuite/cargo_init/workspace_add_member/out/crates/foo/src/main.rs new file mode 100644 index 000000000..e7a11a969 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_init/workspace_add_member/out/crates/foo/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/src/tools/cargo/tests/testsuite/cargo_init/workspace_add_member/stderr.log b/src/tools/cargo/tests/testsuite/cargo_init/workspace_add_member/stderr.log new file mode 100644 index 000000000..3847e4e4a --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_init/workspace_add_member/stderr.log @@ -0,0 +1 @@ + Created binary (application) package diff --git a/src/tools/cargo/tests/testsuite/cargo_init/workspace_add_member/stdout.log b/src/tools/cargo/tests/testsuite/cargo_init/workspace_add_member/stdout.log new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_init/workspace_add_member/stdout.log diff --git a/src/tools/cargo/tests/testsuite/cargo_install/help/stdout.log b/src/tools/cargo/tests/testsuite/cargo_install/help/stdout.log index 2267c5f6b..5e3458d37 100644 --- a/src/tools/cargo/tests/testsuite/cargo_install/help/stdout.log +++ b/src/tools/cargo/tests/testsuite/cargo_install/help/stdout.log @@ -1,9 +1,9 @@ Install a Rust binary. Default location is $HOME/.cargo/bin -Usage: cargo[EXE] install [OPTIONS] [crate]... +Usage: cargo[EXE] install [OPTIONS] [CRATE[@<VER>]]... Arguments: - [crate]... + [CRATE[@<VER>]]... Select the package from the given source Options: --version <VERSION> Specify a version to install diff --git a/src/tools/cargo/tests/testsuite/cargo_login/help/stdout.log b/src/tools/cargo/tests/testsuite/cargo_login/help/stdout.log index fd0f3eb3d..e0d5e7e69 100644 --- a/src/tools/cargo/tests/testsuite/cargo_login/help/stdout.log +++ b/src/tools/cargo/tests/testsuite/cargo_login/help/stdout.log @@ -1,9 +1,9 @@ Log in to a registry. -Usage: cargo[EXE] login [OPTIONS] [token] [-- [args]...] +Usage: cargo[EXE] login [OPTIONS] [TOKEN] [-- [args]...] Arguments: - [token] + [TOKEN] [args]... Additional arguments for the credential provider Options: diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_previous_items/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_previous_items/in/Cargo.toml new file mode 100644 index 000000000..0614e21f7 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_previous_items/in/Cargo.toml @@ -0,0 +1,5 @@ +[workspace] +resolver = "2" + +[workspace.package] +authors = ["Rustaceans"] diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_previous_items/mod.rs b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_previous_items/mod.rs new file mode 100644 index 000000000..9b9642468 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_previous_items/mod.rs @@ -0,0 +1,22 @@ +use cargo_test_support::compare::assert_ui; +use cargo_test_support::curr_dir; +use cargo_test_support::CargoCommand; +use cargo_test_support::Project; + +#[cargo_test] +fn case() { + let project = Project::from_template(curr_dir!().join("in")); + let project_root = project.root(); + let cwd = &project_root; + + snapbox::cmd::Command::cargo_ui() + .arg("new") + .args(["crates/foo"]) + .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/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_previous_items/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_previous_items/out/Cargo.toml new file mode 100644 index 000000000..8df793a4f --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_previous_items/out/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] +resolver = "2" +members = ["crates/foo"] + +[workspace.package] +authors = ["Rustaceans"] diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_previous_items/out/crates/foo/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_previous_items/out/crates/foo/Cargo.toml new file mode 100644 index 000000000..d97480c7c --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_previous_items/out/crates/foo/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "foo" +version = "0.1.0" +edition = "2021" +authors.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_previous_items/out/crates/foo/src/main.rs b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_previous_items/out/crates/foo/src/main.rs new file mode 100644 index 000000000..e7a11a969 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_previous_items/out/crates/foo/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_previous_items/stderr.log b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_previous_items/stderr.log new file mode 100644 index 000000000..90150cdf5 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_previous_items/stderr.log @@ -0,0 +1 @@ + Created binary (application) `crates/foo` package diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_previous_items/stdout.log b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_previous_items/stdout.log new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_previous_items/stdout.log diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_sorted/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_sorted/in/Cargo.toml new file mode 100644 index 000000000..1f3dfe4de --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_sorted/in/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +resolver = "2" +members = ["crates/bar", "crates/qux"] diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_sorted/in/crates/bar/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_sorted/in/crates/bar/Cargo.toml new file mode 100644 index 000000000..825efac34 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_sorted/in/crates/bar/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "bar" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_sorted/in/crates/bar/src/main.rs b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_sorted/in/crates/bar/src/main.rs new file mode 100644 index 000000000..e7a11a969 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_sorted/in/crates/bar/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_sorted/in/crates/qux/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_sorted/in/crates/qux/Cargo.toml new file mode 100644 index 000000000..30a9d52b2 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_sorted/in/crates/qux/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "qux" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_sorted/in/crates/qux/src/main.rs b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_sorted/in/crates/qux/src/main.rs new file mode 100644 index 000000000..e7a11a969 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_sorted/in/crates/qux/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_sorted/mod.rs b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_sorted/mod.rs new file mode 100644 index 000000000..9b9642468 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_sorted/mod.rs @@ -0,0 +1,22 @@ +use cargo_test_support::compare::assert_ui; +use cargo_test_support::curr_dir; +use cargo_test_support::CargoCommand; +use cargo_test_support::Project; + +#[cargo_test] +fn case() { + let project = Project::from_template(curr_dir!().join("in")); + let project_root = project.root(); + let cwd = &project_root; + + snapbox::cmd::Command::cargo_ui() + .arg("new") + .args(["crates/foo"]) + .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/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_sorted/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_sorted/out/Cargo.toml new file mode 100644 index 000000000..cf27071a9 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_sorted/out/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +resolver = "2" +members = ["crates/bar", "crates/foo", "crates/qux"] diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_sorted/out/crates/foo/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_sorted/out/crates/foo/Cargo.toml new file mode 100644 index 000000000..1d9cfe317 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_sorted/out/crates/foo/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "foo" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_sorted/out/crates/foo/src/main.rs b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_sorted/out/crates/foo/src/main.rs new file mode 100644 index 000000000..e7a11a969 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_sorted/out/crates/foo/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_sorted/stderr.log b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_sorted/stderr.log new file mode 100644 index 000000000..90150cdf5 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_sorted/stderr.log @@ -0,0 +1 @@ + Created binary (application) `crates/foo` package diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_sorted/stdout.log b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_sorted/stdout.log new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_format_sorted/stdout.log diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_absolute_package_path/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_absolute_package_path/in/Cargo.toml new file mode 100644 index 000000000..226fd80bc --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_absolute_package_path/in/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +resolver = "2" +members = [] diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_absolute_package_path/mod.rs b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_absolute_package_path/mod.rs new file mode 100644 index 000000000..8bf91be45 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_absolute_package_path/mod.rs @@ -0,0 +1,23 @@ +use cargo_test_support::compare::assert_ui; +use cargo_test_support::curr_dir; +use cargo_test_support::CargoCommand; +use cargo_test_support::Project; + +#[cargo_test] +fn case() { + let project = Project::from_template(curr_dir!().join("in")); + let project_root = project.root(); + let cwd = &project_root; + let package_path = cwd.join("crates").join("foo"); + + snapbox::cmd::Command::cargo_ui() + .arg("new") + .args([package_path]) + .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/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_absolute_package_path/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_absolute_package_path/out/Cargo.toml new file mode 100644 index 000000000..18a4e7cf2 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_absolute_package_path/out/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +resolver = "2" +members = ["crates/foo"] diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_absolute_package_path/out/crates/foo/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_absolute_package_path/out/crates/foo/Cargo.toml new file mode 100644 index 000000000..1d9cfe317 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_absolute_package_path/out/crates/foo/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "foo" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_absolute_package_path/out/crates/foo/src/main.rs b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_absolute_package_path/out/crates/foo/src/main.rs new file mode 100644 index 000000000..e7a11a969 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_absolute_package_path/out/crates/foo/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_absolute_package_path/stderr.log b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_absolute_package_path/stderr.log new file mode 100644 index 000000000..c93b25acb --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_absolute_package_path/stderr.log @@ -0,0 +1 @@ + Created binary (application) `[ROOT]/case/crates/foo` package diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_absolute_package_path/stdout.log b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_absolute_package_path/stdout.log new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_absolute_package_path/stdout.log diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_empty_members/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_empty_members/in/Cargo.toml new file mode 100644 index 000000000..226fd80bc --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_empty_members/in/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +resolver = "2" +members = [] diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_empty_members/mod.rs b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_empty_members/mod.rs new file mode 100644 index 000000000..9b9642468 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_empty_members/mod.rs @@ -0,0 +1,22 @@ +use cargo_test_support::compare::assert_ui; +use cargo_test_support::curr_dir; +use cargo_test_support::CargoCommand; +use cargo_test_support::Project; + +#[cargo_test] +fn case() { + let project = Project::from_template(curr_dir!().join("in")); + let project_root = project.root(); + let cwd = &project_root; + + snapbox::cmd::Command::cargo_ui() + .arg("new") + .args(["crates/foo"]) + .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/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_empty_members/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_empty_members/out/Cargo.toml new file mode 100644 index 000000000..18a4e7cf2 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_empty_members/out/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +resolver = "2" +members = ["crates/foo"] diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_empty_members/out/crates/foo/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_empty_members/out/crates/foo/Cargo.toml new file mode 100644 index 000000000..1d9cfe317 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_empty_members/out/crates/foo/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "foo" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_empty_members/out/crates/foo/src/main.rs b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_empty_members/out/crates/foo/src/main.rs new file mode 100644 index 000000000..e7a11a969 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_empty_members/out/crates/foo/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_empty_members/stderr.log b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_empty_members/stderr.log new file mode 100644 index 000000000..90150cdf5 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_empty_members/stderr.log @@ -0,0 +1 @@ + Created binary (application) `crates/foo` package diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_empty_members/stdout.log b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_empty_members/stdout.log new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_empty_members/stdout.log diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_exclude_list/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_exclude_list/in/Cargo.toml new file mode 100644 index 000000000..4790076a8 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_exclude_list/in/Cargo.toml @@ -0,0 +1,4 @@ +[workspace] +resolver = "2" +members = [] +exclude = ["crates/foo"] diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_exclude_list/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_exclude_list/in/src/lib.rs new file mode 100644 index 000000000..7d12d9af8 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_exclude_list/in/src/lib.rs @@ -0,0 +1,14 @@ +pub fn add(left: usize, right: usize) -> usize { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_exclude_list/mod.rs b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_exclude_list/mod.rs new file mode 100644 index 000000000..9b9642468 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_exclude_list/mod.rs @@ -0,0 +1,22 @@ +use cargo_test_support::compare::assert_ui; +use cargo_test_support::curr_dir; +use cargo_test_support::CargoCommand; +use cargo_test_support::Project; + +#[cargo_test] +fn case() { + let project = Project::from_template(curr_dir!().join("in")); + let project_root = project.root(); + let cwd = &project_root; + + snapbox::cmd::Command::cargo_ui() + .arg("new") + .args(["crates/foo"]) + .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/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_exclude_list/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_exclude_list/out/Cargo.toml new file mode 100644 index 000000000..4790076a8 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_exclude_list/out/Cargo.toml @@ -0,0 +1,4 @@ +[workspace] +resolver = "2" +members = [] +exclude = ["crates/foo"] diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_exclude_list/out/crates/foo/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_exclude_list/out/crates/foo/Cargo.toml new file mode 100644 index 000000000..1d9cfe317 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_exclude_list/out/crates/foo/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "foo" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_exclude_list/out/crates/foo/src/main.rs b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_exclude_list/out/crates/foo/src/main.rs new file mode 100644 index 000000000..e7a11a969 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_exclude_list/out/crates/foo/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_exclude_list/stderr.log b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_exclude_list/stderr.log new file mode 100644 index 000000000..90150cdf5 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_exclude_list/stderr.log @@ -0,0 +1 @@ + Created binary (application) `crates/foo` package diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_exclude_list/stdout.log b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_exclude_list/stdout.log new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_exclude_list/stdout.log diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_members_glob/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_members_glob/in/Cargo.toml new file mode 100644 index 000000000..a848b85b4 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_members_glob/in/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +resolver = "2" +members = ["crates/*"] diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_members_glob/mod.rs b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_members_glob/mod.rs new file mode 100644 index 000000000..9b9642468 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_members_glob/mod.rs @@ -0,0 +1,22 @@ +use cargo_test_support::compare::assert_ui; +use cargo_test_support::curr_dir; +use cargo_test_support::CargoCommand; +use cargo_test_support::Project; + +#[cargo_test] +fn case() { + let project = Project::from_template(curr_dir!().join("in")); + let project_root = project.root(); + let cwd = &project_root; + + snapbox::cmd::Command::cargo_ui() + .arg("new") + .args(["crates/foo"]) + .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/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_members_glob/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_members_glob/out/Cargo.toml new file mode 100644 index 000000000..a848b85b4 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_members_glob/out/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +resolver = "2" +members = ["crates/*"] diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_members_glob/out/crates/foo/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_members_glob/out/crates/foo/Cargo.toml new file mode 100644 index 000000000..1d9cfe317 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_members_glob/out/crates/foo/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "foo" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_members_glob/out/crates/foo/src/main.rs b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_members_glob/out/crates/foo/src/main.rs new file mode 100644 index 000000000..e7a11a969 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_members_glob/out/crates/foo/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_members_glob/stderr.log b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_members_glob/stderr.log new file mode 100644 index 000000000..90150cdf5 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_members_glob/stderr.log @@ -0,0 +1 @@ + Created binary (application) `crates/foo` package diff --git a/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_members_glob/stdout.log b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_members_glob/stdout.log new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_new/add_members_to_workspace_with_members_glob/stdout.log diff --git a/src/tools/cargo/tests/testsuite/cargo_new/help/stdout.log b/src/tools/cargo/tests/testsuite/cargo_new/help/stdout.log index a937f619b..3df5eceb8 100644 --- a/src/tools/cargo/tests/testsuite/cargo_new/help/stdout.log +++ b/src/tools/cargo/tests/testsuite/cargo_new/help/stdout.log @@ -1,9 +1,9 @@ Create a new cargo package at <path> -Usage: cargo[EXE] new [OPTIONS] <path> +Usage: cargo[EXE] new [OPTIONS] <PATH> Arguments: - <path> + <PATH> Options: --vcs <VCS> Initialize a new repository for the given version control system, @@ -12,7 +12,7 @@ Options: --bin Use a binary (application) template [default] --lib Use a library template --edition <YEAR> Edition to set for the crate generated [possible values: 2015, 2018, - 2021] + 2021, 2024] --name <NAME> Set the resulting package name, defaults to the directory name --registry <REGISTRY> Registry to use -q, --quiet Do not print cargo log messages diff --git a/src/tools/cargo/tests/testsuite/cargo_new/mod.rs b/src/tools/cargo/tests/testsuite/cargo_new/mod.rs index 969b09f4f..da0304409 100644 --- a/src/tools/cargo/tests/testsuite/cargo_new/mod.rs +++ b/src/tools/cargo/tests/testsuite/cargo_new/mod.rs @@ -1,3 +1,9 @@ +mod add_members_to_workspace_format_previous_items; +mod add_members_to_workspace_format_sorted; +mod add_members_to_workspace_with_absolute_package_path; +mod add_members_to_workspace_with_empty_members; +mod add_members_to_workspace_with_exclude_list; +mod add_members_to_workspace_with_members_glob; mod help; mod inherit_workspace_lints; mod inherit_workspace_package_table; diff --git a/src/tools/cargo/tests/testsuite/cargo_new/not_inherit_workspace_package_table_if_not_members/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_new/not_inherit_workspace_package_table_if_not_members/out/Cargo.toml index 2d204581c..8de6ed4bf 100644 --- a/src/tools/cargo/tests/testsuite/cargo_new/not_inherit_workspace_package_table_if_not_members/out/Cargo.toml +++ b/src/tools/cargo/tests/testsuite/cargo_new/not_inherit_workspace_package_table_if_not_members/out/Cargo.toml @@ -1,3 +1,5 @@ +[workspace] +members = ["foo"] [workspace.package] authors = ["Rustaceans"] description = "foo" diff --git a/src/tools/cargo/tests/testsuite/cargo_new/not_inherit_workspace_package_table_if_not_members/stderr.log b/src/tools/cargo/tests/testsuite/cargo_new/not_inherit_workspace_package_table_if_not_members/stderr.log index 03b1ff6db..9b5015924 100644 --- a/src/tools/cargo/tests/testsuite/cargo_new/not_inherit_workspace_package_table_if_not_members/stderr.log +++ b/src/tools/cargo/tests/testsuite/cargo_new/not_inherit_workspace_package_table_if_not_members/stderr.log @@ -1,9 +1 @@ -warning: compiling this new package may not work due to invalid workspace configuration - -current package believes it's in a workspace when it's not: -current: [ROOT]/case/foo/Cargo.toml -workspace: [ROOT]/case/Cargo.toml - -this may be fixable by adding `foo` to the `workspace.members` array of the manifest located at: [ROOT]/case/Cargo.toml -Alternatively, to keep it out of the workspace, add the package to the `workspace.exclude` array, or add an empty `[workspace]` table to the package's manifest. Created binary (application) `foo` package diff --git a/src/tools/cargo/tests/testsuite/cargo_owner/help/stdout.log b/src/tools/cargo/tests/testsuite/cargo_owner/help/stdout.log index 580be3c88..110df8e9a 100644 --- a/src/tools/cargo/tests/testsuite/cargo_owner/help/stdout.log +++ b/src/tools/cargo/tests/testsuite/cargo_owner/help/stdout.log @@ -1,9 +1,9 @@ Manage the owners of a crate on the registry -Usage: cargo[EXE] owner [OPTIONS] [crate] +Usage: cargo[EXE] owner [OPTIONS] [CRATE] Arguments: - [crate] + [CRATE] Options: -a, --add <LOGIN> Name of a user or team to invite as an owner diff --git a/src/tools/cargo/tests/testsuite/cargo_pkgid/help/stdout.log b/src/tools/cargo/tests/testsuite/cargo_pkgid/help/stdout.log index ed48bb7ea..5971e88dc 100644 --- a/src/tools/cargo/tests/testsuite/cargo_pkgid/help/stdout.log +++ b/src/tools/cargo/tests/testsuite/cargo_pkgid/help/stdout.log @@ -1,9 +1,9 @@ Print a fully qualified package specification -Usage: cargo[EXE] pkgid [OPTIONS] [spec] +Usage: cargo[EXE] pkgid [OPTIONS] [SPEC] Arguments: - [spec] + [SPEC] Options: -q, --quiet Do not print cargo log messages diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/help/stdout.log b/src/tools/cargo/tests/testsuite/cargo_remove/help/stdout.log index 8937fb9f3..47d2c87ad 100644 --- a/src/tools/cargo/tests/testsuite/cargo_remove/help/stdout.log +++ b/src/tools/cargo/tests/testsuite/cargo_remove/help/stdout.log @@ -15,9 +15,9 @@ Options: -h, --help Print help Section: - --dev Remove as development dependency - --build Remove as build dependency - --target <TARGET> Remove as dependency from the given target platform + --dev Remove from dev-dependencies + --build Remove from build-dependencies + --target <TARGET> Remove from target-dependencies Package Selection: -p, --package [<SPEC>] Package to remove from diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_dep/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_dep/stderr.log index eea124d65..9bb137d1f 100644 --- a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_dep/stderr.log +++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_dep/stderr.log @@ -1,2 +1,2 @@ Removing invalid_dependency_name from dependencies -error: the dependency `invalid_dependency_name` could not be found in `dependencies`. +error: the dependency `invalid_dependency_name` could not be found in `dependencies` diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section/stderr.log index fff5ff00a..8cbafa98e 100644 --- a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section/stderr.log +++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section/stderr.log @@ -1,2 +1,2 @@ Removing docopt from build-dependencies -error: the dependency `docopt` could not be found in `build-dependencies`. +error: the dependency `docopt` could not be found in `build-dependencies`; it is present in `dependencies` diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section_dep/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section_dep/stderr.log index 1926f9577..60c0f8b41 100644 --- a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section_dep/stderr.log +++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_section_dep/stderr.log @@ -1,2 +1,2 @@ Removing semver from dev-dependencies -error: the dependency `semver` could not be found in `dev-dependencies`. +error: the dependency `semver` could not be found in `dev-dependencies`; it is present in `dependencies` diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target/stderr.log index 5075b80b7..eae9f7887 100644 --- a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target/stderr.log +++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target/stderr.log @@ -1,2 +1,2 @@ Removing dbus from dependencies for target `powerpc-unknown-linux-gnu` -error: the dependency `dbus` could not be found in `target.powerpc-unknown-linux-gnu.dependencies`. +error: the dependency `dbus` could not be found in `target.powerpc-unknown-linux-gnu.dependencies`; it is present in `target.x86_64-unknown-linux-gnu.dependencies` diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target_dep/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target_dep/stderr.log index 54bfe085f..635a7bd6c 100644 --- a/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target_dep/stderr.log +++ b/src/tools/cargo/tests/testsuite/cargo_remove/invalid_target_dep/stderr.log @@ -1,2 +1,2 @@ Removing toml from dependencies for target `x86_64-unknown-linux-gnu` -error: the dependency `toml` could not be found in `target.x86_64-unknown-linux-gnu.dependencies`. +error: the dependency `toml` could not be found in `target.x86_64-unknown-linux-gnu.dependencies`; it is present in `dependencies` diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/mod.rs b/src/tools/cargo/tests/testsuite/cargo_remove/mod.rs index 4403e2425..7b9190642 100644 --- a/src/tools/cargo/tests/testsuite/cargo_remove/mod.rs +++ b/src/tools/cargo/tests/testsuite/cargo_remove/mod.rs @@ -20,6 +20,7 @@ mod multiple_dev; mod no_arg; mod offline; mod optional_dep_feature; +mod optional_dep_feature_formatting; mod optional_feature; mod package; mod remove_basic; diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/multiple_dev/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/multiple_dev/out/Cargo.toml index d961b2bb1..b435e33eb 100644 --- a/src/tools/cargo/tests/testsuite/cargo_remove/multiple_dev/out/Cargo.toml +++ b/src/tools/cargo/tests/testsuite/cargo_remove/multiple_dev/out/Cargo.toml @@ -17,4 +17,4 @@ toml = "0.1" clippy = "0.4" [features] -std = ["semver/std"] +std = [ "semver/std"] diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature/out/Cargo.toml index 63112d334..f9613bd30 100644 --- a/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature/out/Cargo.toml +++ b/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature/out/Cargo.toml @@ -20,4 +20,4 @@ clippy = "0.4" regex = "0.1.1" [features] -std = ["semver/std"] +std = [ "semver/std"] diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature_formatting/in/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature_formatting/in/Cargo.toml new file mode 100644 index 000000000..01755d687 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature_formatting/in/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "cargo-remove-test-fixture" +version = "0.1.0" + +[[bin]] +name = "main" +path = "src/main.rs" + +[build-dependencies] +semver = "0.1.0" + +[dependencies] +docopt = { version = "0.6", optional = true } +rustc-serialize = { version = "0.4", optional = true } +semver = "0.1" +toml = { version = "0.1", optional = true } +clippy = { version = "0.4", optional = true } + +[dev-dependencies] +regex = "0.1.1" +serde = "1.0.90" + +[features] +std = [ + # Leading clippy + "dep:clippy", # trailing clippy + + # Leading docopt + "dep:docopt", # trailing docopt + + # Leading rustc-serialize + "dep:rustc-serialize", # trailing rustc-serialize + + # Leading serde/std + "serde/std", # trailing serde/std + + # Leading semver/std + "semver/std", # trailing semver/std + + # Leading toml + "dep:toml", # trailing toml +] diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature_formatting/in/src/lib.rs b/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature_formatting/in/src/lib.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature_formatting/in/src/lib.rs @@ -0,0 +1 @@ + diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature_formatting/mod.rs b/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature_formatting/mod.rs new file mode 100644 index 000000000..69406387b --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature_formatting/mod.rs @@ -0,0 +1,35 @@ +use cargo_test_support::compare::assert_ui; +use cargo_test_support::curr_dir; +use cargo_test_support::CargoCommand; +use cargo_test_support::Project; + +#[cargo_test] +fn case() { + cargo_test_support::registry::init(); + cargo_test_support::registry::Package::new("clippy", "0.4.0+my-package").publish(); + cargo_test_support::registry::Package::new("docopt", "0.6.2+my-package").publish(); + cargo_test_support::registry::Package::new("regex", "0.1.1+my-package").publish(); + cargo_test_support::registry::Package::new("rustc-serialize", "0.4.0+my-package").publish(); + cargo_test_support::registry::Package::new("toml", "0.1.1+my-package").publish(); + cargo_test_support::registry::Package::new("semver", "0.1.1") + .feature("std", &[]) + .publish(); + cargo_test_support::registry::Package::new("serde", "1.0.90") + .feature("std", &[]) + .publish(); + + let project = Project::from_template(curr_dir!().join("in")); + let project_root = project.root(); + let cwd = &project_root; + + snapbox::cmd::Command::cargo_ui() + .arg("remove") + .args(["docopt", "toml"]) + .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/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature_formatting/out/Cargo.toml b/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature_formatting/out/Cargo.toml new file mode 100644 index 000000000..0398c8beb --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature_formatting/out/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "cargo-remove-test-fixture" +version = "0.1.0" + +[[bin]] +name = "main" +path = "src/main.rs" + +[build-dependencies] +semver = "0.1.0" + +[dependencies] +rustc-serialize = { version = "0.4", optional = true } +semver = "0.1" +clippy = { version = "0.4", optional = true } + +[dev-dependencies] +regex = "0.1.1" +serde = "1.0.90" + +[features] +std = [ + # Leading clippy + "dep:clippy", # trailing clippy + + # Leading docopt + # trailing docopt + + # Leading rustc-serialize + "dep:rustc-serialize", # trailing rustc-serialize + + # Leading serde/std + "serde/std", # trailing serde/std + + # Leading semver/std + "semver/std", # trailing semver/std + + # Leading toml + # trailing toml +] diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature_formatting/stderr.log b/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature_formatting/stderr.log new file mode 100644 index 000000000..7bceb0f94 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature_formatting/stderr.log @@ -0,0 +1,2 @@ + Removing docopt from dependencies + Removing toml from dependencies diff --git a/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature_formatting/stdout.log b/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature_formatting/stdout.log new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/tools/cargo/tests/testsuite/cargo_remove/optional_dep_feature_formatting/stdout.log diff --git a/src/tools/cargo/tests/testsuite/cargo_run/help/stdout.log b/src/tools/cargo/tests/testsuite/cargo_run/help/stdout.log index 4b39f30b3..97c13382a 100644 --- a/src/tools/cargo/tests/testsuite/cargo_run/help/stdout.log +++ b/src/tools/cargo/tests/testsuite/cargo_run/help/stdout.log @@ -1,9 +1,9 @@ Run a binary or example of the local package -Usage: cargo[EXE] run [OPTIONS] [args]... +Usage: cargo[EXE] run [OPTIONS] [ARGS]... Arguments: - [args]... Arguments for the binary or example to run + [ARGS]... Arguments for the binary or example to run Options: --ignore-rust-version Ignore `rust-version` specification in packages diff --git a/src/tools/cargo/tests/testsuite/cargo_rustc/help/stdout.log b/src/tools/cargo/tests/testsuite/cargo_rustc/help/stdout.log index 9d43841fe..60069f526 100644 --- a/src/tools/cargo/tests/testsuite/cargo_rustc/help/stdout.log +++ b/src/tools/cargo/tests/testsuite/cargo_rustc/help/stdout.log @@ -1,9 +1,9 @@ Compile a package, and pass extra options to the compiler -Usage: cargo[EXE] rustc [OPTIONS] [args]... +Usage: cargo[EXE] rustc [OPTIONS] [ARGS]... Arguments: - [args]... Extra rustc flags + [ARGS]... Extra rustc flags Options: --print <INFO> Output compiler information without compiling @@ -28,9 +28,9 @@ Target Selection: --bin [<NAME>] Build only the specified binary --examples Build all examples --example [<NAME>] Build only the specified example - --tests Build all tests + --tests Build all test targets --test [<NAME>] Build only the specified test target - --benches Build all benches + --benches Build all bench targets --bench [<NAME>] Build only the specified bench target --all-targets Build all targets diff --git a/src/tools/cargo/tests/testsuite/cargo_rustdoc/help/stdout.log b/src/tools/cargo/tests/testsuite/cargo_rustdoc/help/stdout.log index 706072f24..67ee27e6b 100644 --- a/src/tools/cargo/tests/testsuite/cargo_rustdoc/help/stdout.log +++ b/src/tools/cargo/tests/testsuite/cargo_rustdoc/help/stdout.log @@ -1,9 +1,9 @@ Build a package's documentation, using specified custom flags. -Usage: cargo[EXE] rustdoc [OPTIONS] [args]... +Usage: cargo[EXE] rustdoc [OPTIONS] [ARGS]... Arguments: - [args]... Extra rustdoc flags + [ARGS]... Extra rustdoc flags Options: --open Opens the docs in a browser after the operation @@ -26,9 +26,9 @@ Target Selection: --bin [<NAME>] Build only the specified binary --examples Build all examples --example [<NAME>] Build only the specified example - --tests Build all tests + --tests Build all test targets --test [<NAME>] Build only the specified test target - --benches Build all benches + --benches Build all bench targets --bench [<NAME>] Build only the specified bench target --all-targets Build all targets diff --git a/src/tools/cargo/tests/testsuite/cargo_search/help/stdout.log b/src/tools/cargo/tests/testsuite/cargo_search/help/stdout.log index 07170ad70..9cc508bba 100644 --- a/src/tools/cargo/tests/testsuite/cargo_search/help/stdout.log +++ b/src/tools/cargo/tests/testsuite/cargo_search/help/stdout.log @@ -1,9 +1,9 @@ Search packages in crates.io -Usage: cargo[EXE] search [OPTIONS] [query]... +Usage: cargo[EXE] search [OPTIONS] [QUERY]... Arguments: - [query]... + [QUERY]... Options: --limit <LIMIT> Limit the number of results (default: 10, max: 100) diff --git a/src/tools/cargo/tests/testsuite/cargo_test/help/stdout.log b/src/tools/cargo/tests/testsuite/cargo_test/help/stdout.log index 5df62d6bb..d7ec18f46 100644 --- a/src/tools/cargo/tests/testsuite/cargo_test/help/stdout.log +++ b/src/tools/cargo/tests/testsuite/cargo_test/help/stdout.log @@ -1,10 +1,10 @@ Execute all unit and integration tests and build examples of a local package -Usage: cargo[EXE] test [OPTIONS] [TESTNAME] [-- [args]...] +Usage: cargo[EXE] test [OPTIONS] [TESTNAME] [-- [ARGS]...] Arguments: [TESTNAME] If specified, only run tests containing this string in their names - [args]... Arguments for the test binary + [ARGS]... Arguments for the test binary Options: --doc Test only this library's documentation @@ -33,9 +33,9 @@ Target Selection: --bin [<NAME>] Test only the specified binary --examples Test all examples --example [<NAME>] Test only the specified example - --tests Test all tests + --tests Test all test targets --test [<NAME>] Test only the specified test target - --benches Test all benches + --benches Test all bench targets --bench [<NAME>] Test only the specified bench target --all-targets Test all targets (does not include doctests) diff --git a/src/tools/cargo/tests/testsuite/cargo_test/no_keep_going/stderr.log b/src/tools/cargo/tests/testsuite/cargo_test/no_keep_going/stderr.log index fd4ca9b2a..22323e651 100644 --- a/src/tools/cargo/tests/testsuite/cargo_test/no_keep_going/stderr.log +++ b/src/tools/cargo/tests/testsuite/cargo_test/no_keep_going/stderr.log @@ -2,6 +2,6 @@ error: unexpected argument '--keep-going' found tip: use `--no-fail-fast` to run as many tests as possible regardless of failure -Usage: cargo[EXE] test [OPTIONS] [TESTNAME] [-- [args]...] +Usage: cargo[EXE] test [OPTIONS] [TESTNAME] [-- [ARGS]...] For more information, try '--help'. diff --git a/src/tools/cargo/tests/testsuite/cargo_uninstall/help/stdout.log b/src/tools/cargo/tests/testsuite/cargo_uninstall/help/stdout.log index 2da1a5d57..efdf11c03 100644 --- a/src/tools/cargo/tests/testsuite/cargo_uninstall/help/stdout.log +++ b/src/tools/cargo/tests/testsuite/cargo_uninstall/help/stdout.log @@ -1,9 +1,9 @@ Remove a Rust binary -Usage: cargo[EXE] uninstall [OPTIONS] [spec]... +Usage: cargo[EXE] uninstall [OPTIONS] [SPEC]... Arguments: - [spec]... + [SPEC]... Options: --root <DIR> Directory to uninstall packages from diff --git a/src/tools/cargo/tests/testsuite/cargo_yank/help/stdout.log b/src/tools/cargo/tests/testsuite/cargo_yank/help/stdout.log index c6dbfeb9d..61dc800c7 100644 --- a/src/tools/cargo/tests/testsuite/cargo_yank/help/stdout.log +++ b/src/tools/cargo/tests/testsuite/cargo_yank/help/stdout.log @@ -1,9 +1,9 @@ Remove a pushed crate from the index -Usage: cargo[EXE] yank [OPTIONS] [crate] +Usage: cargo[EXE] yank [OPTIONS] [CRATE] Arguments: - [crate] + [CRATE] Options: --version <VERSION> The version to yank or un-yank diff --git a/src/tools/cargo/tests/testsuite/check.rs b/src/tools/cargo/tests/testsuite/check.rs index b74bd6209..03611ae67 100644 --- a/src/tools/cargo/tests/testsuite/check.rs +++ b/src/tools/cargo/tests/testsuite/check.rs @@ -1496,3 +1496,26 @@ fn check_unused_manifest_keys() { ) .run(); } + +#[cargo_test] +fn versionless_package() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + description = "foo" + "#, + ) + .file("src/lib.rs", "") + .build(); + p.cargo("check") + .with_stderr( + "\ +[CHECKING] foo v0.0.0 ([CWD]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); +} diff --git a/src/tools/cargo/tests/testsuite/check_cfg.rs b/src/tools/cargo/tests/testsuite/check_cfg.rs index c35da637d..57d5f8053 100644 --- a/src/tools/cargo/tests/testsuite/check_cfg.rs +++ b/src/tools/cargo/tests/testsuite/check_cfg.rs @@ -15,16 +15,16 @@ macro_rules! x { $what, '(', $($who,)* ')', "'", "[..]") } }}; - ($tool:tt => $what:tt of $who:tt with $($values:tt)*) => {{ + ($tool:tt => $what:tt of $who:tt with $($first_value:tt $($other_values:tt)*)?) => {{ #[cfg(windows)] { concat!("[RUNNING] [..]", $tool, "[..] --check-cfg \"", - $what, '(', $who, $(", ", "/\"", $values, "/\"",)* ")", '"', "[..]") + $what, '(', $who, ", values(", $("/\"", $first_value, "/\"", $(", ", "/\"", $other_values, "/\"",)*)* "))", '"', "[..]") } #[cfg(not(windows))] { concat!("[RUNNING] [..]", $tool, "[..] --check-cfg '", - $what, '(', $who, $(", ", "\"", $values, "\"",)* ")", "'", "[..]") + $what, '(', $who, ", values(", $("\"", $first_value, "\"", $(", ", "\"", $other_values, "\"",)*)* "))", "'", "[..]") } }}; } @@ -47,9 +47,9 @@ fn features() { .file("src/main.rs", "fn main() {}") .build(); - p.cargo("check -v -Zcheck-cfg=features") + p.cargo("check -v -Zcheck-cfg") .masquerade_as_nightly_cargo(&["check-cfg"]) - .with_stderr_contains(x!("rustc" => "values" of "feature" with "f_a" "f_b")) + .with_stderr_contains(x!("rustc" => "cfg" of "feature" with "f_a" "f_b")) .run(); } @@ -76,10 +76,9 @@ fn features_with_deps() { .file("bar/src/lib.rs", "#[allow(dead_code)] fn bar() {}") .build(); - p.cargo("check -v -Zcheck-cfg=features") + p.cargo("check -v -Zcheck-cfg") .masquerade_as_nightly_cargo(&["check-cfg"]) - .with_stderr_contains(x!("rustc" => "values" of "feature")) - .with_stderr_contains(x!("rustc" => "values" of "feature" with "f_a" "f_b")) + .with_stderr_contains(x!("rustc" => "cfg" of "feature" with "f_a" "f_b")) .run(); } @@ -107,10 +106,9 @@ fn features_with_opt_deps() { .file("bar/src/lib.rs", "#[allow(dead_code)] fn bar() {}") .build(); - p.cargo("check -v -Zcheck-cfg=features") + p.cargo("check -v -Zcheck-cfg") .masquerade_as_nightly_cargo(&["check-cfg"]) - .with_stderr_contains(x!("rustc" => "values" of "feature")) - .with_stderr_contains(x!("rustc" => "values" of "feature" with "bar" "default" "f_a" "f_b")) + .with_stderr_contains(x!("rustc" => "cfg" of "feature" with "bar" "default" "f_a" "f_b")) .run(); } @@ -137,111 +135,22 @@ fn features_with_namespaced_features() { .file("bar/src/lib.rs", "#[allow(dead_code)] fn bar() {}") .build(); - p.cargo("check -v -Zcheck-cfg=features") + p.cargo("check -v -Zcheck-cfg") .masquerade_as_nightly_cargo(&["check-cfg"]) - .with_stderr_contains(x!("rustc" => "values" of "feature" with "f_a" "f_b")) + .with_stderr_contains(x!("rustc" => "cfg" of "feature" with "f_a" "f_b")) .run(); } #[cargo_test(nightly, reason = "--check-cfg is unstable")] -fn well_known_names() { +fn well_known_names_values() { let p = project() .file("Cargo.toml", &basic_manifest("foo", "0.1.0")) .file("src/main.rs", "fn main() {}") .build(); - p.cargo("check -v -Zcheck-cfg=names") + p.cargo("check -v -Zcheck-cfg") .masquerade_as_nightly_cargo(&["check-cfg"]) - .with_stderr_contains(x!("rustc" => "names")) - .run(); -} - -#[cargo_test(nightly, reason = "--check-cfg is unstable")] -fn well_known_values() { - let p = project() - .file("Cargo.toml", &basic_manifest("foo", "0.1.0")) - .file("src/main.rs", "fn main() {}") - .build(); - - p.cargo("check -v -Zcheck-cfg=values") - .masquerade_as_nightly_cargo(&["check-cfg"]) - .with_stderr_contains(x!("rustc" => "values")) - .run(); -} - -#[cargo_test(nightly, reason = "--check-cfg is unstable")] -fn cli_all_options() { - let p = project() - .file( - "Cargo.toml", - r#" - [package] - name = "foo" - version = "0.1.0" - - [features] - f_a = [] - f_b = [] - "#, - ) - .file("src/main.rs", "fn main() {}") - .build(); - - p.cargo("check -v -Zcheck-cfg=features,names,values") - .masquerade_as_nightly_cargo(&["check-cfg"]) - .with_stderr_contains(x!("rustc" => "names")) - .with_stderr_contains(x!("rustc" => "values")) - .with_stderr_contains(x!("rustc" => "values" of "feature" with "f_a" "f_b")) - .run(); -} - -#[cargo_test(nightly, reason = "--check-cfg is unstable")] -fn features_with_cargo_check() { - let p = project() - .file( - "Cargo.toml", - r#" - [package] - name = "foo" - version = "0.1.0" - - [features] - f_a = [] - f_b = [] - "#, - ) - .file("src/main.rs", "fn main() {}") - .build(); - - p.cargo("check -v -Zcheck-cfg=features") - .masquerade_as_nightly_cargo(&["check-cfg"]) - .with_stderr_contains(x!("rustc" => "values" of "feature" with "f_a" "f_b")) - .run(); -} - -#[cargo_test(nightly, reason = "--check-cfg is unstable")] -fn well_known_names_with_check() { - let p = project() - .file("Cargo.toml", &basic_manifest("foo", "0.1.0")) - .file("src/main.rs", "fn main() {}") - .build(); - - p.cargo("check -v -Zcheck-cfg=names") - .masquerade_as_nightly_cargo(&["check-cfg"]) - .with_stderr_contains(x!("rustc" => "names")) - .run(); -} - -#[cargo_test(nightly, reason = "--check-cfg is unstable")] -fn well_known_values_with_check() { - let p = project() - .file("Cargo.toml", &basic_manifest("foo", "0.1.0")) - .file("src/main.rs", "fn main() {}") - .build(); - - p.cargo("check -v -Zcheck-cfg=values") - .masquerade_as_nightly_cargo(&["check-cfg"]) - .with_stderr_contains(x!("rustc" => "values")) + .with_stderr_contains(x!("rustc" => "cfg" of "feature" with)) .run(); } @@ -263,9 +172,9 @@ fn features_test() { .file("src/main.rs", "fn main() {}") .build(); - p.cargo("test -v -Zcheck-cfg=features") + p.cargo("test -v -Zcheck-cfg") .masquerade_as_nightly_cargo(&["check-cfg"]) - .with_stderr_contains(x!("rustc" => "values" of "feature" with "f_a" "f_b")) + .with_stderr_contains(x!("rustc" => "cfg" of "feature" with "f_a" "f_b")) .run(); } @@ -288,64 +197,37 @@ fn features_doctest() { .file("src/lib.rs", "#[allow(dead_code)] fn foo() {}") .build(); - p.cargo("test -v --doc -Zcheck-cfg=features") + p.cargo("test -v --doc -Zcheck-cfg") .masquerade_as_nightly_cargo(&["check-cfg"]) - .with_stderr_contains(x!("rustc" => "values" of "feature" with "default" "f_a" "f_b")) - .with_stderr_contains(x!("rustdoc" => "values" of "feature" with "default" "f_a" "f_b")) + .with_stderr_contains(x!("rustc" => "cfg" of "feature" with "default" "f_a" "f_b")) + .with_stderr_contains(x!("rustdoc" => "cfg" of "feature" with "default" "f_a" "f_b")) .run(); } #[cargo_test(nightly, reason = "--check-cfg is unstable")] -fn well_known_names_test() { +fn well_known_names_values_test() { let p = project() .file("Cargo.toml", &basic_manifest("foo", "0.1.0")) .file("src/main.rs", "fn main() {}") .build(); - p.cargo("test -v -Zcheck-cfg=names") + p.cargo("test -v -Zcheck-cfg") .masquerade_as_nightly_cargo(&["check-cfg"]) - .with_stderr_contains(x!("rustc" => "names")) + .with_stderr_contains(x!("rustc" => "cfg" of "feature" with)) .run(); } #[cargo_test(nightly, reason = "--check-cfg is unstable")] -fn well_known_values_test() { - let p = project() - .file("Cargo.toml", &basic_manifest("foo", "0.1.0")) - .file("src/main.rs", "fn main() {}") - .build(); - - p.cargo("test -v -Zcheck-cfg=values") - .masquerade_as_nightly_cargo(&["check-cfg"]) - .with_stderr_contains(x!("rustc" => "values")) - .run(); -} - -#[cargo_test(nightly, reason = "--check-cfg is unstable")] -fn well_known_names_doctest() { +fn well_known_names_values_doctest() { let p = project() .file("Cargo.toml", &basic_manifest("foo", "0.1.0")) .file("src/lib.rs", "#[allow(dead_code)] fn foo() {}") .build(); - p.cargo("test -v --doc -Zcheck-cfg=names") + p.cargo("test -v --doc -Zcheck-cfg") .masquerade_as_nightly_cargo(&["check-cfg"]) - .with_stderr_contains(x!("rustc" => "names")) - .with_stderr_contains(x!("rustdoc" => "names")) - .run(); -} - -#[cargo_test(nightly, reason = "--check-cfg is unstable")] -fn well_known_values_doctest() { - let p = project() - .file("Cargo.toml", &basic_manifest("foo", "0.1.0")) - .file("src/lib.rs", "#[allow(dead_code)] fn foo() {}") - .build(); - - p.cargo("test -v --doc -Zcheck-cfg=values") - .masquerade_as_nightly_cargo(&["check-cfg"]) - .with_stderr_contains(x!("rustc" => "values")) - .with_stderr_contains(x!("rustdoc" => "values")) + .with_stderr_contains(x!("rustc" => "cfg" of "feature" with)) + .with_stderr_contains(x!("rustdoc" => "cfg" of "feature" with)) .run(); } @@ -368,9 +250,9 @@ fn features_doc() { .file("src/lib.rs", "#[allow(dead_code)] fn foo() {}") .build(); - p.cargo("doc -v -Zcheck-cfg=features") + p.cargo("doc -v -Zcheck-cfg") .masquerade_as_nightly_cargo(&["check-cfg"]) - .with_stderr_contains(x!("rustdoc" => "values" of "feature" with "default" "f_a" "f_b")) + .with_stderr_contains(x!("rustdoc" => "cfg" of "feature" with "default" "f_a" "f_b")) .run(); } @@ -390,13 +272,13 @@ fn build_script_feedback() { .file("src/main.rs", "fn main() {}") .file( "build.rs", - r#"fn main() { println!("cargo:rustc-check-cfg=names(foo)"); }"#, + r#"fn main() { println!("cargo:rustc-check-cfg=cfg(foo)"); }"#, ) .build(); - p.cargo("check -v -Zcheck-cfg=output") + p.cargo("check -v -Zcheck-cfg") .masquerade_as_nightly_cargo(&["check-cfg"]) - .with_stderr_contains(x!("rustc" => "names" of "foo")) + .with_stderr_contains(x!("rustc" => "cfg" of "foo")) .run(); } @@ -416,12 +298,13 @@ fn build_script_doc() { .file("src/main.rs", "fn main() {}") .file( "build.rs", - r#"fn main() { println!("cargo:rustc-check-cfg=names(foo)"); }"#, + r#"fn main() { println!("cargo:rustc-check-cfg=cfg(foo)"); }"#, ) .build(); - p.cargo("doc -v -Zcheck-cfg=output") + + p.cargo("doc -v -Zcheck-cfg") .with_stderr_does_not_contain("rustc [..] --check-cfg [..]") - .with_stderr_contains(x!("rustdoc" => "names" of "foo")) + .with_stderr_contains(x!("rustdoc" => "cfg" of "foo")) .with_stderr( "\ [COMPILING] foo v0.0.1 ([CWD]) @@ -429,7 +312,9 @@ fn build_script_doc() { [RUNNING] `[..]/build-script-build` [DOCUMENTING] foo [..] [RUNNING] `rustdoc [..] src/main.rs [..] -[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]", +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/foo/index.html +", ) .masquerade_as_nightly_cargo(&["check-cfg"]) .run(); @@ -458,19 +343,54 @@ fn build_script_override() { &format!( r#" [target.{}.a] - rustc-check-cfg = ["names(foo)"] + rustc-check-cfg = ["cfg(foo)"] "#, target ), ) .build(); - p.cargo("check -v -Zcheck-cfg=output") - .with_stderr_contains(x!("rustc" => "names" of "foo")) + p.cargo("check -v -Zcheck-cfg") + .with_stderr_contains(x!("rustc" => "cfg" of "foo")) .masquerade_as_nightly_cargo(&["check-cfg"]) .run(); } +#[cargo_test] +fn build_script_override_feature_gate() { + let target = cargo_test_support::rustc_host(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + links = "a" + "#, + ) + .file("src/main.rs", "fn main() {}") + .file("build.rs", "fn main() {}") + .file( + ".cargo/config", + &format!( + r#" + [target.{}.a] + rustc-check-cfg = ["cfg(foo)"] + "#, + target + ), + ) + .build(); + + p.cargo("check") + .with_stderr_contains( + "warning: target config[..]rustc-check-cfg[..] requires -Zcheck-cfg flag", + ) + .run(); +} + #[cargo_test(nightly, reason = "--check-cfg is unstable")] fn build_script_test() { let p = project() @@ -487,7 +407,7 @@ fn build_script_test() { .file( "build.rs", r#"fn main() { - println!("cargo:rustc-check-cfg=names(foo)"); + println!("cargo:rustc-check-cfg=cfg(foo)"); println!("cargo:rustc-cfg=foo"); }"#, ) @@ -516,9 +436,9 @@ fn build_script_test() { .file("tests/test.rs", "#[cfg(foo)] #[test] fn test_bar() {}") .build(); - p.cargo("test -v -Zcheck-cfg=output") - .with_stderr_contains(x!("rustc" => "names" of "foo")) - .with_stderr_contains(x!("rustdoc" => "names" of "foo")) + p.cargo("test -v -Zcheck-cfg") + .with_stderr_contains(x!("rustc" => "cfg" of "foo")) + .with_stderr_contains(x!("rustdoc" => "cfg" of "foo")) .with_stdout_contains("test test_foo ... ok") .with_stdout_contains("test test_bar ... ok") .with_stdout_contains_n("test [..] ... ok", 3) @@ -526,6 +446,34 @@ fn build_script_test() { .run(); } +#[cargo_test] +fn build_script_feature_gate() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + build = "build.rs" + "#, + ) + .file( + "build.rs", + r#"fn main() { + println!("cargo:rustc-check-cfg=cfg(foo)"); + println!("cargo:rustc-cfg=foo"); + }"#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("check") + .with_stderr_contains("warning[..]cargo:rustc-check-cfg requires -Zcheck-cfg flag") + .with_status(0) + .run(); +} + #[cargo_test(nightly, reason = "--check-cfg is unstable")] fn config_valid() { let p = project() @@ -546,16 +494,14 @@ fn config_valid() { ".cargo/config.toml", r#" [unstable] - check-cfg = ["features", "names", "values"] + check-cfg = true "#, ) .build(); - p.cargo("check -v -Zcheck-cfg=features,names,values") + p.cargo("check -v") .masquerade_as_nightly_cargo(&["check-cfg"]) - .with_stderr_contains(x!("rustc" => "names")) - .with_stderr_contains(x!("rustc" => "values")) - .with_stderr_contains(x!("rustc" => "values" of "feature" with "f_a" "f_b")) + .with_stderr_contains(x!("rustc" => "cfg" of "feature" with "f_a" "f_b")) .run(); } @@ -582,7 +528,36 @@ fn config_invalid() { p.cargo("check") .masquerade_as_nightly_cargo(&["check-cfg"]) - .with_stderr_contains("error: unstable check-cfg only takes `features`, `names`, `values` or `output` as valid inputs") + .with_stderr_contains("error:[..]`unstable.check-cfg` expected true/false[..]") .with_status(101) .run(); } + +#[cargo_test] +fn config_feature_gate() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [features] + f_a = [] + "#, + ) + .file("src/main.rs", "fn main() {}") + .file( + ".cargo/config.toml", + r#" + [unstable] + check-cfg = true + "#, + ) + .build(); + + p.cargo("check -v") + .with_stderr_does_not_contain("--check-cfg") + .run(); +} diff --git a/src/tools/cargo/tests/testsuite/collisions.rs b/src/tools/cargo/tests/testsuite/collisions.rs index 77e05dd9c..986619eb2 100644 --- a/src/tools/cargo/tests/testsuite/collisions.rs +++ b/src/tools/cargo/tests/testsuite/collisions.rs @@ -202,6 +202,7 @@ fn collision_doc_multiple_versions() { [DOCUMENTING] bar v2.0.0 [FINISHED] [..] [DOCUMENTING] foo v0.1.0 [..] +[GENERATED] [CWD]/target/doc/foo/index.html ", ) .run(); @@ -384,6 +385,7 @@ fn collision_doc_profile_split() { [DOCUMENTING] pm v0.1.0 [..] [DOCUMENTING] foo v0.1.0 [..] [FINISHED] [..] +[GENERATED] [CWD]/target/doc/foo/index.html ", ) .run(); @@ -430,6 +432,7 @@ the same path; see <https://github.com/rust-lang/cargo/issues/6313>. [CHECKING] bar v1.0.0 [DOCUMENTING] foo v0.1.0 [..] [FINISHED] [..] +[GENERATED] [CWD]/target/doc/foo/index.html ", ) .run(); @@ -478,6 +481,7 @@ fn collision_doc_target() { [CHECKING] bar v1.0.0 [DOCUMENTING] foo v0.1.0 [..] [FINISHED] [..] +[GENERATED] [CWD]/target/[..]/doc/foo/index.html ", ) .run(); @@ -545,6 +549,8 @@ the same path; see <https://github.com/rust-lang/cargo/issues/6313>. [DOCUMENTING] foo-macro v1.0.0 [..] [DOCUMENTING] abc v1.0.0 [..] [FINISHED] [..] +[GENERATED] [CWD]/target/doc/abc/index.html +[GENERATED] [CWD]/target/doc/foo_macro/index.html ") .run(); } diff --git a/src/tools/cargo/tests/testsuite/config.rs b/src/tools/cargo/tests/testsuite/config.rs index 7078fc445..e5078bd8e 100644 --- a/src/tools/cargo/tests/testsuite/config.rs +++ b/src/tools/cargo/tests/testsuite/config.rs @@ -2,8 +2,9 @@ use cargo::core::{PackageIdSpec, Shell}; use cargo::util::config::{self, Config, Definition, JobsConfig, SslVersionConfig, StringList}; -use cargo::util::interning::InternedString; -use cargo::util::toml::{self as cargo_toml, TomlDebugInfo, VecStringOrBool as VSOB}; +use cargo::util::toml::schema::TomlTrimPaths; +use cargo::util::toml::schema::TomlTrimPathsValue; +use cargo::util::toml::schema::{self as cargo_toml, TomlDebugInfo, VecStringOrBool as VSOB}; use cargo::CargoResult; use cargo_test_support::compare; use cargo_test_support::{panic_error, paths, project, symlink_supported, t}; @@ -21,6 +22,7 @@ pub struct ConfigBuilder { unstable: Vec<String>, config_args: Vec<String>, cwd: Option<PathBuf>, + root: Option<PathBuf>, enable_nightly_features: bool, } @@ -30,6 +32,7 @@ impl ConfigBuilder { env: HashMap::new(), unstable: Vec::new(), config_args: Vec::new(), + root: None, cwd: None, enable_nightly_features: false, } @@ -60,8 +63,28 @@ impl ConfigBuilder { } /// Sets the current working directory where config files will be loaded. + /// + /// Default is the root from [`ConfigBuilder::root`] or [`paths::root`]. pub fn cwd(&mut self, path: impl AsRef<Path>) -> &mut Self { - self.cwd = Some(paths::root().join(path.as_ref())); + let path = path.as_ref(); + let cwd = self + .root + .as_ref() + .map_or_else(|| paths::root().join(path), |r| r.join(path)); + self.cwd = Some(cwd); + self + } + + /// Sets the test root directory. + /// + /// This generally should not be necessary. It is only useful if you want + /// to create a `Config` from within a thread. Since Cargo's testsuite + /// uses thread-local storage, this can be used to avoid accessing that + /// thread-local storage. + /// + /// Default is [`paths::root`]. + pub fn root(&mut self, path: impl Into<PathBuf>) -> &mut Self { + self.root = Some(path.into()); self } @@ -72,14 +95,15 @@ impl ConfigBuilder { /// Creates the `Config`, returning a Result. pub fn build_err(&self) -> CargoResult<Config> { - let output = Box::new(fs::File::create(paths::root().join("shell.out")).unwrap()); + let root = self.root.clone().unwrap_or_else(|| paths::root()); + let output = Box::new(fs::File::create(root.join("shell.out")).unwrap()); let shell = Shell::from_write(output); - let cwd = self.cwd.clone().unwrap_or_else(|| paths::root()); - let homedir = paths::home(); + let cwd = self.cwd.clone().unwrap_or_else(|| root.clone()); + let homedir = root.join("home").join(".cargo"); let mut config = Config::new(shell, cwd, homedir); config.nightly_features_allowed = self.enable_nightly_features || !self.unstable.is_empty(); config.set_env(self.env.clone()); - config.set_search_stop_path(paths::root()); + config.set_search_stop_path(&root); config.configure( 0, false, @@ -422,8 +446,8 @@ lto = false p, cargo_toml::TomlProfile { lto: Some(cargo_toml::StringOrBool::Bool(false)), - dir_name: Some(InternedString::new("without-lto")), - inherits: Some(InternedString::new("dev")), + dir_name: Some(String::from("without-lto")), + inherits: Some(String::from("dev")), ..Default::default() } ); @@ -1503,7 +1527,7 @@ fn all_profile_options() { let base_settings = cargo_toml::TomlProfile { opt_level: Some(cargo_toml::TomlOptLevel("0".to_string())), lto: Some(cargo_toml::StringOrBool::String("thin".to_string())), - codegen_backend: Some(InternedString::new("example")), + codegen_backend: Some(String::from("example")), codegen_units: Some(123), debug: Some(cargo_toml::TomlDebugInfo::Limited), split_debuginfo: Some("packed".to_string()), @@ -1512,12 +1536,13 @@ fn all_profile_options() { panic: Some("abort".to_string()), overflow_checks: Some(true), incremental: Some(true), - dir_name: Some(InternedString::new("dir_name")), - inherits: Some(InternedString::new("debug")), + dir_name: Some(String::from("dir_name")), + inherits: Some(String::from("debug")), strip: Some(cargo_toml::StringOrBool::String("symbols".to_string())), package: None, build_override: None, rustflags: None, + trim_paths: None, }; let mut overrides = BTreeMap::new(); let key = cargo_toml::ProfilePackageSpec::Spec(PackageIdSpec::parse("foo").unwrap()); @@ -1705,3 +1730,63 @@ jobs = 2 JobsConfig::Integer(v) => assert_eq!(v, 2), } } + +#[cargo_test] +fn trim_paths_parsing() { + let config = ConfigBuilder::new().build(); + let p: cargo_toml::TomlProfile = config.get("profile.dev").unwrap(); + assert_eq!(p.trim_paths, None); + + let test_cases = [ + (TomlTrimPathsValue::Diagnostics.into(), "diagnostics"), + (TomlTrimPathsValue::Macro.into(), "macro"), + (TomlTrimPathsValue::Object.into(), "object"), + ]; + for (expected, val) in test_cases { + // env + let config = ConfigBuilder::new() + .env("CARGO_PROFILE_DEV_TRIM_PATHS", val) + .build(); + let trim_paths: TomlTrimPaths = config.get("profile.dev.trim-paths").unwrap(); + assert_eq!(trim_paths, expected, "failed to parse {val}"); + + // config.toml + let config = ConfigBuilder::new() + .config_arg(format!("profile.dev.trim-paths='{val}'")) + .build(); + let trim_paths: TomlTrimPaths = config.get("profile.dev.trim-paths").unwrap(); + assert_eq!(trim_paths, expected, "failed to parse {val}"); + } + + let test_cases = [(TomlTrimPaths::none(), false), (TomlTrimPaths::All, true)]; + + for (expected, val) in test_cases { + // env + let config = ConfigBuilder::new() + .env("CARGO_PROFILE_DEV_TRIM_PATHS", format!("{val}")) + .build(); + let trim_paths: TomlTrimPaths = config.get("profile.dev.trim-paths").unwrap(); + assert_eq!(trim_paths, expected, "failed to parse {val}"); + + // config.toml + let config = ConfigBuilder::new() + .config_arg(format!("profile.dev.trim-paths={val}")) + .build(); + let trim_paths: TomlTrimPaths = config.get("profile.dev.trim-paths").unwrap(); + assert_eq!(trim_paths, expected, "failed to parse {val}"); + } + + let expected = vec![ + TomlTrimPathsValue::Diagnostics, + TomlTrimPathsValue::Macro, + TomlTrimPathsValue::Object, + ] + .into(); + let val = r#"["diagnostics", "macro", "object"]"#; + // config.toml + let config = ConfigBuilder::new() + .config_arg(format!("profile.dev.trim-paths={val}")) + .build(); + let trim_paths: TomlTrimPaths = config.get("profile.dev.trim-paths").unwrap(); + assert_eq!(trim_paths, expected, "failed to parse {val}"); +} diff --git a/src/tools/cargo/tests/testsuite/cross_compile.rs b/src/tools/cargo/tests/testsuite/cross_compile.rs index 1bc0c277d..b57ba2c7a 100644 --- a/src/tools/cargo/tests/testsuite/cross_compile.rs +++ b/src/tools/cargo/tests/testsuite/cross_compile.rs @@ -411,92 +411,6 @@ fn linker() { .run(); } -#[cargo_test(nightly, reason = "plugins are unstable")] -fn plugin_with_extra_dylib_dep() { - if cross_compile::disabled() { - return; - } - - let foo = project() - .file( - "Cargo.toml", - r#" - [package] - name = "foo" - version = "0.0.1" - authors = [] - - [dependencies.bar] - path = "../bar" - "#, - ) - .file( - "src/main.rs", - r#" - #![feature(plugin)] - #![plugin(bar)] - - fn main() {} - "#, - ) - .build(); - let _bar = project() - .at("bar") - .file( - "Cargo.toml", - r#" - [package] - name = "bar" - version = "0.0.1" - authors = [] - - [lib] - name = "bar" - plugin = true - - [dependencies.baz] - path = "../baz" - "#, - ) - .file( - "src/lib.rs", - r#" - #![feature(rustc_private)] - - extern crate baz; - extern crate rustc_driver; - - use rustc_driver::plugin::Registry; - - #[no_mangle] - pub fn __rustc_plugin_registrar(reg: &mut Registry) { - println!("{}", baz::baz()); - } - "#, - ) - .build(); - let _baz = project() - .at("baz") - .file( - "Cargo.toml", - r#" - [package] - name = "baz" - version = "0.0.1" - authors = [] - - [lib] - name = "baz" - crate_type = ["dylib"] - "#, - ) - .file("src/lib.rs", "pub fn baz() -> i32 { 1 }") - .build(); - - let target = cross_compile::alternate(); - foo.cargo("build --target").arg(&target).run(); -} - #[cargo_test] fn cross_tests() { if !cross_compile::can_run_on_host() { diff --git a/src/tools/cargo/tests/testsuite/custom_target.rs b/src/tools/cargo/tests/testsuite/custom_target.rs index 491d3233c..a04029075 100644 --- a/src/tools/cargo/tests/testsuite/custom_target.rs +++ b/src/tools/cargo/tests/testsuite/custom_target.rs @@ -116,6 +116,8 @@ fn custom_target_dependency() { } #[cargo_test(nightly, reason = "requires features no_core, lang_items")] +// This is randomly crashing in lld. See https://github.com/rust-lang/rust/issues/115985 +#[cfg_attr(all(windows, target_env = "gnu"), ignore = "windows-gnu lld crashing")] fn custom_bin_target() { let p = project() .file( diff --git a/src/tools/cargo/tests/testsuite/death.rs b/src/tools/cargo/tests/testsuite/death.rs index f0e182d01..b61896dc9 100644 --- a/src/tools/cargo/tests/testsuite/death.rs +++ b/src/tools/cargo/tests/testsuite/death.rs @@ -1,12 +1,12 @@ //! Tests for ctrl-C handling. +use cargo_test_support::{project, slow_cpu_multiplier}; use std::fs; use std::io::{self, Read}; use std::net::TcpListener; use std::process::{Child, Stdio}; use std::thread; - -use cargo_test_support::{project, slow_cpu_multiplier}; +use std::time; #[cargo_test] fn ctrl_c_kills_everyone() { @@ -87,6 +87,155 @@ fn ctrl_c_kills_everyone() { ); } +#[cargo_test] +fn kill_cargo_add_never_corrupts_cargo_toml() { + cargo_test_support::registry::init(); + cargo_test_support::registry::Package::new("my-package", "0.1.1+my-package").publish(); + + let with_dependency = r#" +[package] +name = "foo" +version = "0.0.1" +authors = [] + +[dependencies] +my-package = "0.1.1" +"#; + let without_dependency = r#" +[package] +name = "foo" +version = "0.0.1" +authors = [] +"#; + + for sleep_time_ms in [30, 60, 90] { + let p = project() + .file("Cargo.toml", without_dependency) + .file("src/lib.rs", "") + .build(); + + let mut cargo = p.cargo("add").arg("my-package").build_command(); + cargo + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + let mut child = cargo.spawn().unwrap(); + + thread::sleep(time::Duration::from_millis(sleep_time_ms)); + + assert!(child.kill().is_ok()); + assert!(child.wait().is_ok()); + + // check the Cargo.toml + let contents = fs::read(p.root().join("Cargo.toml")).unwrap(); + + // not empty + assert_ne!( + contents, b"", + "Cargo.toml is empty, and should not be at {} milliseconds", + sleep_time_ms + ); + + // We should have the original Cargo.toml or the new one, nothing else. + if std::str::from_utf8(&contents) + .unwrap() + .contains("[dependencies]") + { + assert_eq!( + std::str::from_utf8(&contents).unwrap(), + with_dependency, + "Cargo.toml is with_dependency after add at {} milliseconds", + sleep_time_ms + ); + } else { + assert_eq!( + std::str::from_utf8(&contents).unwrap(), + without_dependency, + "Cargo.toml is without_dependency after add at {} milliseconds", + sleep_time_ms + ); + } + } +} + +#[cargo_test] +fn kill_cargo_remove_never_corrupts_cargo_toml() { + let with_dependency = r#" +[package] +name = "foo" +version = "0.0.1" +authors = [] +build = "build.rs" + +[dependencies] +bar = "0.0.1" +"#; + let without_dependency = r#" +[package] +name = "foo" +version = "0.0.1" +authors = [] +build = "build.rs" +"#; + + // This test depends on killing the cargo process at the right time to cause a failed write. + // Note that we're iterating and using the index as time in ms to sleep before killing the cargo process. + // If it is working correctly, we never fail, but can't hang out here all day... + // So we'll just run it a few times and hope for the best. + for sleep_time_ms in [30, 60, 90] { + // new basic project with a single dependency + let p = project() + .file("Cargo.toml", with_dependency) + .file("src/lib.rs", "") + .build(); + + // run cargo remove the dependency + let mut cargo = p.cargo("remove").arg("bar").build_command(); + cargo + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + let mut child = cargo.spawn().unwrap(); + + thread::sleep(time::Duration::from_millis(sleep_time_ms)); + + assert!(child.kill().is_ok()); + assert!(child.wait().is_ok()); + + // check the Cargo.toml + let contents = fs::read(p.root().join("Cargo.toml")).unwrap(); + + // not empty + assert_ne!( + contents, b"", + "Cargo.toml is empty, and should not be at {} milliseconds", + sleep_time_ms + ); + + // We should have the original Cargo.toml or the new one, nothing else. + if std::str::from_utf8(&contents) + .unwrap() + .contains("[dependencies]") + { + assert_eq!( + std::str::from_utf8(&contents).unwrap(), + with_dependency, + "Cargo.toml is not the same as the original at {} milliseconds", + sleep_time_ms + ); + } else { + assert_eq!( + std::str::from_utf8(&contents).unwrap(), + without_dependency, + "Cargo.toml is not the same as expected at {} milliseconds", + sleep_time_ms + ); + } + } +} + #[cfg(unix)] pub fn ctrl_c(child: &mut Child) { let r = unsafe { libc::kill(-(child.id() as i32), libc::SIGINT) }; diff --git a/src/tools/cargo/tests/testsuite/doc.rs b/src/tools/cargo/tests/testsuite/doc.rs index a16980912..65169d214 100644 --- a/src/tools/cargo/tests/testsuite/doc.rs +++ b/src/tools/cargo/tests/testsuite/doc.rs @@ -31,6 +31,7 @@ fn simple() { [..] foo v0.0.1 ([CWD]) [..] foo v0.0.1 ([CWD]) [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/foo/index.html ", ) .run(); @@ -69,6 +70,7 @@ fn doc_twice() { "\ [DOCUMENTING] foo v0.0.1 ([CWD]) [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/foo/index.html ", ) .run(); @@ -103,6 +105,7 @@ fn doc_deps() { [..] bar v0.0.1 ([CWD]/bar) [DOCUMENTING] foo v0.0.1 ([CWD]) [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/foo/index.html ", ) .run(); @@ -151,6 +154,7 @@ fn doc_no_deps() { [CHECKING] bar v0.0.1 ([CWD]/bar) [DOCUMENTING] foo v0.0.1 ([CWD]) [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/foo/index.html ", ) .run(); @@ -284,6 +288,8 @@ the same path; see <https://github.com/rust-lang/cargo/issues/6313>. [DOCUMENTING] bar v0.1.0 ([ROOT]/foo/bar) [DOCUMENTING] foo v0.1.0 ([ROOT]/foo/foo) [FINISHED] [..] +[GENERATED] [CWD]/target/doc/foo_lib/index.html +[GENERATED] [CWD]/target/doc/foo_lib/index.html ", ) .run(); @@ -398,6 +404,7 @@ fn doc_lib_bin_same_name_documents_lib() { "\ [DOCUMENTING] foo v0.0.1 ([CWD]) [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/foo/index.html ", ) .run(); @@ -433,6 +440,7 @@ fn doc_lib_bin_same_name_documents_lib_when_requested() { "\ [DOCUMENTING] foo v0.0.1 ([CWD]) [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/foo/index.html ", ) .run(); @@ -478,6 +486,7 @@ the same path; see <https://github.com/rust-lang/cargo/issues/6313>. [CHECKING] foo v0.0.1 ([CWD]) [DOCUMENTING] foo v0.0.1 ([CWD]) [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/foo/index.html ", ) .run(); @@ -523,6 +532,7 @@ the same path; see <https://github.com/rust-lang/cargo/issues/6313>. [CHECKING] foo v0.0.1 ([CWD]) [DOCUMENTING] foo v0.0.1 ([CWD]) [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/foo/index.html ", ) .run(); @@ -567,7 +577,9 @@ fn doc_lib_bin_example_same_name_documents_named_example_when_requested() { "\ [CHECKING] foo v0.0.1 ([CWD]) [DOCUMENTING] foo v0.0.1 ([CWD]) -[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]", +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/ex1/index.html +", ) .run(); @@ -620,7 +632,10 @@ fn doc_lib_bin_example_same_name_documents_examples_when_requested() { "\ [CHECKING] foo v0.0.1 ([CWD]) [DOCUMENTING] foo v0.0.1 ([CWD]) -[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]", +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/ex1/index.html +[GENERATED] [CWD]/target/doc/ex2/index.html +", ) .run(); @@ -677,6 +692,7 @@ fn doc_dash_p() { [..] b v0.0.1 ([CWD]/b) [DOCUMENTING] a v0.0.1 ([CWD]/a) [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/a/index.html ", ) .run(); @@ -704,6 +720,7 @@ fn doc_all_exclude() { "\ [DOCUMENTING] bar v0.1.0 ([..]) [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/bar/index.html ", ) .run(); @@ -731,6 +748,7 @@ fn doc_all_exclude_glob() { "\ [DOCUMENTING] bar v0.1.0 ([..]) [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/bar/index.html ", ) .run(); @@ -918,6 +936,7 @@ fn doc_release() { [DOCUMENTING] foo v0.0.1 ([..]) [RUNNING] `rustdoc [..] src/lib.rs [..]` [FINISHED] release [optimized] target(s) in [..] +[GENERATED] [CWD]/target/doc/foo/index.html ", ) .run(); @@ -1006,6 +1025,7 @@ fn features() { [DOCUMENTING] bar v0.0.1 [..] [DOCUMENTING] foo v0.0.1 [..] [FINISHED] [..] +[GENERATED] [CWD]/target/doc/foo/index.html ", ) .run(); @@ -1020,6 +1040,7 @@ fn features() { [DOCUMENTING] bar v0.0.1 [..] [DOCUMENTING] foo v0.0.1 [..] [FINISHED] [..] +[GENERATED] [CWD]/target/doc/foo/index.html ", ) .run(); @@ -1032,6 +1053,7 @@ fn features() { [DOCUMENTING] bar v0.0.1 [..] [DOCUMENTING] foo v0.0.1 [..] [FINISHED] [..] +[GENERATED] [CWD]/target/doc/foo/index.html ", ) .run(); @@ -1202,6 +1224,7 @@ fn doc_virtual_manifest_one_project() { "\ [DOCUMENTING] bar v0.1.0 ([..]) [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/bar/index.html ", ) .run(); @@ -1229,6 +1252,7 @@ fn doc_virtual_manifest_glob() { "\ [DOCUMENTING] baz v0.1.0 ([..]) [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/baz/index.html ", ) .run(); @@ -1277,6 +1301,7 @@ the same path; see <https://github.com/rust-lang/cargo/issues/6313>. [CHECKING] bar v0.1.0 [DOCUMENTING] bar v0.1.0 [..] [FINISHED] [..] +[GENERATED] [CWD]/target/doc/bar/index.html ", ) .run(); @@ -1639,6 +1664,7 @@ fn doc_cap_lints() { [CHECKING] a v0.5.0 ([..]) [DOCUMENTING] foo v0.0.1 ([..]) [FINISHED] dev [..] +[GENERATED] [CWD]/target/doc/foo/index.html ", ) .run(); @@ -1903,6 +1929,7 @@ fn bin_private_items() { "\ [DOCUMENTING] foo v0.0.1 ([CWD]) [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/foo/index.html ", ) .run(); @@ -1963,6 +1990,7 @@ fn bin_private_items_deps() { [CHECKING] bar v0.0.1 ([..]) [DOCUMENTING] foo v0.0.1 ([CWD]) [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/foo/index.html ", ) .run(); @@ -1997,6 +2025,7 @@ fn crate_versions() { [DOCUMENTING] foo v1.2.4 [..] [RUNNING] `rustdoc --crate-type lib --crate-name foo src/lib.rs [..]--crate-version 1.2.4` [FINISHED] [..] +[GENERATED] [CWD]/target/doc/foo/index.html ", ) .run(); @@ -2406,7 +2435,8 @@ fn doc_fingerprint_unusual_behavior() { p.cargo("doc") .with_stderr( "[DOCUMENTING] foo [..]\n\ - [FINISHED] [..]", + [FINISHED] [..]\n\ + [GENERATED] [CWD]/target/doc/foo/index.html", ) .run(); // This will delete somefile, but not .hidden. @@ -2425,7 +2455,8 @@ fn doc_fingerprint_unusual_behavior() { .masquerade_as_nightly_cargo(&["skip-rustdoc-fingerprint"]) .with_stderr( "[DOCUMENTING] foo [..]\n\ - [FINISHED] [..]", + [FINISHED] [..]\n\ + [GENERATED] [CWD]/target/doc/foo/index.html", ) .run(); // Should not have deleted anything. @@ -2467,6 +2498,8 @@ fn lib_before_bin() { [RUNNING] `rustdoc --crate-type lib --crate-name foo src/lib.rs [..] [RUNNING] `rustdoc --crate-type bin --crate-name somebin src/bin/somebin.rs [..] [FINISHED] [..] +[GENERATED] [CWD]/target/doc/foo/index.html +[GENERATED] [CWD]/target/doc/somebin/index.html ", ) .run(); @@ -2517,6 +2550,7 @@ fn doc_lib_false() { [CHECKING] foo v0.1.0 [..] [DOCUMENTING] foo v0.1.0 [..] [FINISHED] [..] +[GENERATED] [CWD]/target/doc/some_bin/index.html ", ) .run(); @@ -2563,6 +2597,7 @@ fn doc_lib_false_dep() { [CHECKING] bar v0.1.0 [..] [DOCUMENTING] foo v0.1.0 [..] [FINISHED] [..] +[GENERATED] [CWD]/target/doc/foo/index.html ", ) .run(); @@ -2587,7 +2622,8 @@ fn link_to_private_item() { p.cargo("doc") .with_stderr( "[DOCUMENTING] foo [..]\n\ - [FINISHED] [..]", + [FINISHED] [..]\n\ + [GENERATED] [CWD]/target/doc/foo/index.html", ) .run(); } diff --git a/src/tools/cargo/tests/testsuite/docscrape.rs b/src/tools/cargo/tests/testsuite/docscrape.rs index c536a6738..d4d011ff3 100644 --- a/src/tools/cargo/tests/testsuite/docscrape.rs +++ b/src/tools/cargo/tests/testsuite/docscrape.rs @@ -26,13 +26,18 @@ fn basic() { [SCRAPING] foo v0.0.1 ([CWD]) [DOCUMENTING] foo v0.0.1 ([CWD]) [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/foo/index.html ", ) .run(); p.cargo("doc -Zunstable-options -Z rustdoc-scrape-examples") .masquerade_as_nightly_cargo(&["rustdoc-scrape-examples"]) - .with_stderr("[FINISHED] [..]") + .with_stderr( + "[FINISHED] [..] +[GENERATED] [CWD]/target/doc/foo/index.html +", + ) .run(); let doc_html = p.read_file("target/doc/foo/fn.foo.html"); @@ -311,6 +316,7 @@ fn cache() { [SCRAPING] foo v0.0.1 ([CWD]) [DOCUMENTING] foo v0.0.1 ([CWD]) [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/foo/index.html ", ) .run(); @@ -320,6 +326,7 @@ fn cache() { .with_stderr( "\ [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/foo/index.html ", ) .run(); @@ -361,7 +368,9 @@ warning: failed to scan example \"ex2\" in package `foo` for example code usage If an example should not be scanned, then consider adding `doc-scrape-examples = false` to its `[[example]]` definition in Cargo.toml warning: `foo` (example \"ex2\") generated 1 warning [DOCUMENTING] foo v0.0.1 ([CWD]) -[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]", +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/foo/index.html +", ) .run(); } @@ -425,7 +434,9 @@ warning: failed to scan example \"ex1\" in package `foo` for example code usage If an example should not be scanned, then consider adding `doc-scrape-examples = false` to its `[[example]]` definition in Cargo.toml warning: `foo` (example \"ex1\") generated 1 warning [DOCUMENTING] foo v0.0.1 ([CWD]) -[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]", +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/foo/index.html +", ) .run(); @@ -448,7 +459,9 @@ error: expected one of `!` or `::`, found `NOT` | ^^^ expected one of `!` or `::` [DOCUMENTING] foo v0.0.1 ([CWD]) -[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]", +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/foo/index.html +", ) .run(); @@ -499,7 +512,9 @@ warning: Rustdoc did not scrape the following examples because they require dev- If you want Rustdoc to scrape these examples, then add `doc-scrape-examples = true` to the [[example]] target configuration of at least one example. [DOCUMENTING] foo v0.0.1 ([CWD]) -[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]", +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/foo/index.html +", ) .run(); @@ -513,7 +528,9 @@ warning: Rustdoc did not scrape the following examples because they require dev- [DOCUMENTING] a v0.0.1 ([CWD]/a) [SCRAPING] foo v0.0.1 ([CWD]) [DOCUMENTING] foo v0.0.1 ([CWD]) -[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]", +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/ex/index.html +", ) .run(); } @@ -560,7 +577,9 @@ fn use_dev_deps_if_explicitly_enabled() { [CHECKING] a v0.0.1 ([CWD]/a) [SCRAPING] foo v0.0.1 ([CWD]) [DOCUMENTING] foo v0.0.1 ([CWD]) -[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]", +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/foo/index.html +", ) .run(); } diff --git a/src/tools/cargo/tests/testsuite/features.rs b/src/tools/cargo/tests/testsuite/features.rs index 557fab14a..4b7455c37 100644 --- a/src/tools/cargo/tests/testsuite/features.rs +++ b/src/tools/cargo/tests/testsuite/features.rs @@ -36,6 +36,37 @@ Caused by: } #[cargo_test] +fn empty_feature_name() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [features] + "" = [] + "#, + ) + .file("src/main.rs", "") + .build(); + + p.cargo("check") + .with_status(101) + .with_stderr( + "\ +[ERROR] failed to parse manifest at `[..]` + +Caused by: + feature name cannot be empty +", + ) + .run(); +} + +#[cargo_test] fn same_name() { // Feature with the same name as a dependency. let p = project() @@ -1144,6 +1175,61 @@ fn activating_feature_activates_dep() { } #[cargo_test] +fn activating_feature_does_not_activate_transitive_dev_dependency() { + let p = project() + .no_manifest() + .file( + "a/Cargo.toml", + r#" + [package] + name = "a" + version = "0.0.0" + edition = "2021" + + [features] + f = ["b/f"] + + [dependencies] + b = { path = "../b" } + "#, + ) + .file( + "b/Cargo.toml", + r#" + [package] + name = "b" + version = "0.0.0" + edition = "2021" + + [features] + f = ["c/f"] + + [dev-dependencies] + c = { path = "../c" } + "#, + ) + .file( + "c/Cargo.toml", + r#" + [package] + name = "c" + version = "0.0.0" + edition = "2021" + + [features] + f = [] + "#, + ) + .file("a/src/lib.rs", "") + .file("b/src/lib.rs", "") + .file("c/src/lib.rs", "compile_error!") + .build(); + + p.cargo("check --manifest-path a/Cargo.toml --features f") + .run(); +} + +#[cargo_test] fn dep_feature_in_cmd_line() { let p = project() .file( @@ -1990,7 +2076,7 @@ error: failed to parse manifest at `[ROOT]/foo/Cargo.toml` Caused by: invalid character `&` in feature `a&b` in package foo v0.1.0 ([ROOT]/foo), \ - characters must be Unicode XID characters, `+`, or `.` \ + characters must be Unicode XID characters, '-', `+`, or `.` \ (numbers, `+`, `-`, `_`, `.`, or most letters) ", ) diff --git a/src/tools/cargo/tests/testsuite/features2.rs b/src/tools/cargo/tests/testsuite/features2.rs index 9238de2c6..125a293a0 100644 --- a/src/tools/cargo/tests/testsuite/features2.rs +++ b/src/tools/cargo/tests/testsuite/features2.rs @@ -1807,7 +1807,7 @@ fn shared_dep_same_but_dependencies() { [COMPILING] dep [..] [COMPILING] bin2 [..] [COMPILING] bin1 [..] -warning: feat: enabled +warning: bin2@0.1.0: feat: enabled [FINISHED] [..] ", ) @@ -1823,7 +1823,7 @@ warning: feat: enabled [FRESH] subdep [..] [FRESH] dep [..] [FRESH] bin1 [..] -warning: feat: enabled +warning: bin2@0.1.0: feat: enabled [FRESH] bin2 [..] [FINISHED] [..] ", @@ -1955,6 +1955,7 @@ fn doc_optional() { [CHECKING] bar v1.0.0 [DOCUMENTING] foo v0.1.0 [..] [FINISHED] [..] +[GENERATED] [CWD]/target/doc/foo/index.html ", ) .run(); diff --git a/src/tools/cargo/tests/testsuite/glob_targets.rs b/src/tools/cargo/tests/testsuite/glob_targets.rs index 8021dffa9..1eed4b1fa 100644 --- a/src/tools/cargo/tests/testsuite/glob_targets.rs +++ b/src/tools/cargo/tests/testsuite/glob_targets.rs @@ -137,6 +137,7 @@ fn doc_bin() { [DOCUMENTING] foo v0.0.1 ([CWD]) [RUNNING] `rustdoc --crate-type bin --crate-name bin1 [..]` [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/bin1/index.html ", ) .run(); @@ -407,6 +408,7 @@ fn rustdoc_example() { [DOCUMENTING] foo v0.0.1 ([CWD]) [RUNNING] `rustdoc --crate-type bin --crate-name example1 [..]` [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/example1/index.html ", ) .run(); @@ -421,6 +423,7 @@ fn rustdoc_bin() { [DOCUMENTING] foo v0.0.1 ([CWD]) [RUNNING] `rustdoc --crate-type bin --crate-name bin1 [..]` [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/bin1/index.html ", ) .run(); @@ -435,6 +438,7 @@ fn rustdoc_bench() { [DOCUMENTING] foo v0.0.1 ([CWD]) [RUNNING] `rustdoc --crate-type bin --crate-name bench1 [..]` [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/bench1/index.html ", ) .run(); @@ -449,6 +453,7 @@ fn rustdoc_test() { [DOCUMENTING] foo v0.0.1 ([CWD]) [RUNNING] `rustdoc --crate-type bin --crate-name test1 [..]` [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/test1/index.html ", ) .run(); diff --git a/src/tools/cargo/tests/testsuite/install.rs b/src/tools/cargo/tests/testsuite/install.rs index 0a3670e6c..fd53b607b 100644 --- a/src/tools/cargo/tests/testsuite/install.rs +++ b/src/tools/cargo/tests/testsuite/install.rs @@ -58,6 +58,28 @@ fn simple() { } #[cargo_test] +fn install_the_same_version_twice() { + pkg("foo", "0.0.1"); + + cargo_process("install foo foo") + .with_stderr( + "\ +[UPDATING] `[..]` index +[DOWNLOADING] crates ... +[DOWNLOADED] foo v0.0.1 (registry [..]) +[INSTALLING] foo v0.0.1 +[COMPILING] foo v0.0.1 +[FINISHED] release [optimized] target(s) in [..] +[INSTALLING] [CWD]/home/.cargo/bin/foo[EXE] +[INSTALLED] package `foo v0.0.1` (executable `foo[EXE]`) +[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries +", + ) + .run(); + assert_has_installed_exe(cargo_home(), "foo"); +} + +#[cargo_test] fn toolchain() { pkg("foo", "0.0.1"); @@ -1614,7 +1636,7 @@ fn inline_version_without_name() { cargo_process("install @0.1.1") .with_status(1) .with_stderr( - "error: invalid value '@0.1.1' for '[crate]...': missing crate name before '@' + "error: invalid value '@0.1.1' for '[CRATE[@<VER>]]...': missing crate name before '@' For more information, try '--help'. ", @@ -1844,7 +1866,9 @@ fn install_empty_argument() { cargo_process("install") .arg("") .with_status(1) - .with_stderr_contains("[ERROR] invalid value '' for '[crate]...': crate name is empty") + .with_stderr_contains( + "[ERROR] invalid value '' for '[CRATE[@<VER>]]...': crate name is empty", + ) .run(); } @@ -2455,7 +2479,7 @@ error: unexpected argument '--release' found tip: `--release` is the default for `cargo install`; instead `--debug` is supported -Usage: cargo[EXE] install [OPTIONS] [crate]... +Usage: cargo[EXE] install [OPTIONS] [CRATE[@<VER>]]... For more information, try '--help'. ", @@ -2463,3 +2487,23 @@ For more information, try '--help'. .with_status(1) .run(); } + +#[cargo_test] +fn install_incompat_msrv() { + Package::new("foo", "0.1.0") + .file("src/main.rs", "fn main() {}") + .rust_version("1.30") + .publish(); + Package::new("foo", "0.2.0") + .file("src/main.rs", "fn main() {}") + .rust_version("1.9876.0") + .publish(); + + cargo_process("install foo") + .with_stderr("\ +[UPDATING] `dummy-registry` index +[ERROR] cannot install package `foo 0.2.0`, it requires rustc 1.9876.0 or newer, while the currently active rustc version is [..] +`foo 0.1.0` supports rustc 1.30 +") + .with_status(101).run(); +} diff --git a/src/tools/cargo/tests/testsuite/install_upgrade.rs b/src/tools/cargo/tests/testsuite/install_upgrade.rs index 580117f5c..fe4f8c6c7 100644 --- a/src/tools/cargo/tests/testsuite/install_upgrade.rs +++ b/src/tools/cargo/tests/testsuite/install_upgrade.rs @@ -230,7 +230,7 @@ fn ambiguous_version_no_longer_allowed() { cargo_process("install foo --version=1.0") .with_stderr( "\ -[ERROR] invalid value '1.0' for '--version <VERSION>': cannot parse '1.0' as a SemVer version +[ERROR] invalid value '1.0' for '--version <VERSION>': unexpected end of input while parsing minor version number tip: if you want to specify SemVer range, add an explicit qualifier, like '^1.0' diff --git a/src/tools/cargo/tests/testsuite/list_availables.rs b/src/tools/cargo/tests/testsuite/list_availables.rs index fe635a19b..ebd6e9c1c 100644 --- a/src/tools/cargo/tests/testsuite/list_availables.rs +++ b/src/tools/cargo/tests/testsuite/list_availables.rs @@ -59,7 +59,7 @@ Available binaries: .with_stderr( "\ error: \"--bench\" takes one argument. -Available benches: +Available bench targets: bench1 bench2 @@ -75,7 +75,7 @@ Available benches: .with_stderr( "\ error: \"--test\" takes one argument. -Available tests: +Available test targets: test1 test2 @@ -139,7 +139,7 @@ No binaries available. .with_stderr( "\ error: \"--bench\" takes one argument. -No benches available. +No bench targets available. ", ) @@ -153,7 +153,7 @@ No benches available. .with_stderr( "\ error: \"--test\" takes one argument. -No tests available. +No test targets available. ", ) diff --git a/src/tools/cargo/tests/testsuite/main.rs b/src/tools/cargo/tests/testsuite/main.rs index 8279f5818..07f749e34 100644 --- a/src/tools/cargo/tests/testsuite/main.rs +++ b/src/tools/cargo/tests/testsuite/main.rs @@ -17,6 +17,7 @@ mod build_plan; mod build_script; mod build_script_env; mod build_script_extra_link_arg; +mod cache_lock; mod cache_messages; mod cargo; mod cargo_add; @@ -131,12 +132,12 @@ mod patch; mod path; mod paths; mod pkgid; -mod plugins; mod proc_macro; mod profile_config; mod profile_custom; mod profile_overrides; mod profile_targets; +mod profile_trim_paths; mod profiles; mod progress; mod pub_priv; diff --git a/src/tools/cargo/tests/testsuite/metadata.rs b/src/tools/cargo/tests/testsuite/metadata.rs index fbead4dea..888cdce8c 100644 --- a/src/tools/cargo/tests/testsuite/metadata.rs +++ b/src/tools/cargo/tests/testsuite/metadata.rs @@ -4257,3 +4257,285 @@ fn workspace_metadata_with_dependencies_no_deps_artifact() { ) .run(); } + +#[cargo_test] +fn versionless_packages() { + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["bar", "baz"] + "#, + ) + .file( + "bar/Cargo.toml", + r#" + [package] + name = "bar" + + [dependencies] + foobar = "0.0.1" + baz = { path = "../baz/" } + "#, + ) + .file("bar/src/lib.rs", "") + .file( + "baz/Cargo.toml", + r#" + [package] + name = "baz" + + [dependencies] + foobar = "0.0.1" + "#, + ) + .file("baz/src/lib.rs", "") + .build(); + Package::new("foobar", "0.0.1").publish(); + + p.cargo("metadata -q --format-version 1") + .with_json( + r#" +{ + "packages": [ + { + "name": "bar", + "version": "0.0.0", + "id": "bar 0.0.0 [..]", + "license": null, + "license_file": null, + "description": null, + "source": null, + "dependencies": [ + { + "name": "baz", + "source": null, + "req": "*", + "kind": null, + "rename": null, + "optional": false, + "uses_default_features": true, + "features": [], + "target": null, + "registry": null, + "path": "[..]/baz" + }, + { + "name": "foobar", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "req": "^0.0.1", + "kind": null, + "rename": null, + "optional": false, + "uses_default_features": true, + "features": [], + "target": null, + "registry": null + } + ], + "targets": [ + { + "kind": [ + "lib" + ], + "crate_types": [ + "lib" + ], + "name": "bar", + "src_path": "[..]/bar/src/lib.rs", + "edition": "2015", + "doc": true, + "doctest": true, + "test": true + } + ], + "features": {}, + "manifest_path": "[..]/bar/Cargo.toml", + "metadata": null, + "publish": [], + "authors": [], + "categories": [], + "keywords": [], + "readme": null, + "repository": null, + "homepage": null, + "documentation": null, + "edition": "2015", + "links": null, + "default_run": null, + "rust_version": null + }, + { + "name": "baz", + "version": "0.0.0", + "id": "baz 0.0.0 [..]", + "license": null, + "license_file": null, + "description": null, + "source": null, + "dependencies": [ + { + "name": "foobar", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "req": "^0.0.1", + "kind": null, + "rename": null, + "optional": false, + "uses_default_features": true, + "features": [], + "target": null, + "registry": null + } + ], + "targets": [ + { + "kind": [ + "lib" + ], + "crate_types": [ + "lib" + ], + "name": "baz", + "src_path": "[..]/baz/src/lib.rs", + "edition": "2015", + "doc": true, + "doctest": true, + "test": true + } + ], + "features": {}, + "manifest_path": "[..]/baz/Cargo.toml", + "metadata": null, + "publish": [], + "authors": [], + "categories": [], + "keywords": [], + "readme": null, + "repository": null, + "homepage": null, + "documentation": null, + "edition": "2015", + "links": null, + "default_run": null, + "rust_version": null + }, + { + "name": "foobar", + "version": "0.0.1", + "id": "foobar 0.0.1 [..]", + "license": null, + "license_file": null, + "description": null, + "source": "registry+https://github.com/rust-lang/crates.io-index", + "dependencies": [], + "targets": [ + { + "kind": [ + "lib" + ], + "crate_types": [ + "lib" + ], + "name": "foobar", + "src_path": "[..]/foobar-0.0.1/src/lib.rs", + "edition": "2015", + "doc": true, + "doctest": true, + "test": true + } + ], + "features": {}, + "manifest_path": "[..]/foobar-0.0.1/Cargo.toml", + "metadata": null, + "publish": null, + "authors": [], + "categories": [], + "keywords": [], + "readme": null, + "repository": null, + "homepage": null, + "documentation": null, + "edition": "2015", + "links": null, + "default_run": null, + "rust_version": null + } + ], + "workspace_members": [ + "bar 0.0.0 [..]", + "baz 0.0.0 [..]" + ], + "workspace_default_members": [ + "bar 0.0.0 [..]", + "baz 0.0.0 [..]" + ], + "resolve": { + "nodes": [ + { + "id": "bar 0.0.0 [..]", + "dependencies": [ + "baz 0.0.0 [..]", + "foobar 0.0.1 [..]" + ], + "deps": [ + { + "name": "baz", + "pkg": "baz 0.0.0 [..]", + "dep_kinds": [ + { + "kind": null, + "target": null + } + ] + }, + { + "name": "foobar", + "pkg": "foobar 0.0.1 [..]", + "dep_kinds": [ + { + "kind": null, + "target": null + } + ] + } + ], + "features": [] + }, + { + "id": "baz 0.0.0 [..]", + "dependencies": [ + "foobar 0.0.1 [..]" + ], + "deps": [ + { + "name": "foobar", + "pkg": "foobar 0.0.1 [..]", + "dep_kinds": [ + { + "kind": null, + "target": null + } + ] + } + ], + "features": [] + }, + { + "id": "foobar 0.0.1 [..]", + "dependencies": [], + "deps": [], + "features": [] + } + ], + "root": null + }, + "target_directory": "[..]/foo/target", + "version": 1, + "workspace_root": "[..]", + "metadata": null +} +"#, + ) + .run(); +} diff --git a/src/tools/cargo/tests/testsuite/multitarget.rs b/src/tools/cargo/tests/testsuite/multitarget.rs index 5f3543f01..30be9e97d 100644 --- a/src/tools/cargo/tests/testsuite/multitarget.rs +++ b/src/tools/cargo/tests/testsuite/multitarget.rs @@ -111,6 +111,34 @@ fn simple_doc() { } #[cargo_test] +fn simple_doc_open() { + if cross_compile::disabled() { + return; + } + let t1 = cross_compile::alternate(); + let t2 = rustc_host(); + let p = project() + .file("Cargo.toml", &basic_manifest("foo", "1.0.0")) + .file("src/lib.rs", "//! empty lib") + .build(); + + p.cargo("doc") + .arg("--open") + .arg("--target") + .arg(&t1) + .arg("--target") + .arg(&t2) + .with_stderr( + "\ +[DOCUMENTING] foo v1.0.0 ([..]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[ERROR] only one `--target` argument is supported", + ) + .with_status(101) + .run(); +} + +#[cargo_test] fn simple_check() { if cross_compile::disabled() { return; diff --git a/src/tools/cargo/tests/testsuite/new.rs b/src/tools/cargo/tests/testsuite/new.rs index 91a2871e9..a34169e9d 100644 --- a/src/tools/cargo/tests/testsuite/new.rs +++ b/src/tools/cargo/tests/testsuite/new.rs @@ -124,7 +124,7 @@ fn no_argument() { .with_stderr_contains( "\ error: the following required arguments were not provided: - <path> + <PATH> ", ) .run(); @@ -451,6 +451,7 @@ fn non_ascii_name() { "\ [WARNING] the name `Привет` contains non-ASCII characters Non-ASCII crate names are not supported by Rust. +[WARNING] the name `Привет` is not snake_case or kebab-case which is recommended for package names, consider `привет` [CREATED] binary (application) `Привет` package ", ) @@ -502,6 +503,29 @@ or change the name in Cargo.toml with: } #[cargo_test] +fn non_snake_case_name() { + cargo_process("new UPPERcase_name") + .with_stderr( + "\ +[WARNING] the name `UPPERcase_name` is not snake_case or kebab-case which is recommended for package names, consider `uppercase_name` +[CREATED] binary (application) `UPPERcase_name` package +", + ) + .run(); +} + +#[cargo_test] +fn kebab_case_name_is_accepted() { + cargo_process("new kebab-case-is-valid") + .with_stderr( + "\ +[CREATED] binary (application) `kebab-case-is-valid` package +", + ) + .run(); +} + +#[cargo_test] fn git_default_branch() { // Check for init.defaultBranch support. create_default_gitconfig(); diff --git a/src/tools/cargo/tests/testsuite/out_dir.rs b/src/tools/cargo/tests/testsuite/out_dir.rs index fe647f56e..83621a2d2 100644 --- a/src/tools/cargo/tests/testsuite/out_dir.rs +++ b/src/tools/cargo/tests/testsuite/out_dir.rs @@ -281,6 +281,29 @@ fn cargo_build_out_dir() { ); } +#[cargo_test] +fn unsupported_short_out_dir_flag() { + let p = project() + .file("src/main.rs", r#"fn main() { println!("Hello, World!") }"#) + .build(); + + p.cargo("build -Z unstable-options -O") + .masquerade_as_nightly_cargo(&["out-dir"]) + .with_stderr( + "\ +error: unexpected argument '-O' found + + tip: a similar argument exists: '--out-dir' + +Usage: cargo[EXE] build [OPTIONS] + +For more information, try '--help'. +", + ) + .with_status(1) + .run(); +} + fn check_dir_contents( out_dir: &Path, expected_linux: &[&str], diff --git a/src/tools/cargo/tests/testsuite/package.rs b/src/tools/cargo/tests/testsuite/package.rs index 010523fda..4ec4fc0d6 100644 --- a/src/tools/cargo/tests/testsuite/package.rs +++ b/src/tools/cargo/tests/testsuite/package.rs @@ -1359,7 +1359,7 @@ Caused by: failed to parse the `edition` key Caused by: - supported edition values are `2015`, `2018`, or `2021`, but `chicken` is unknown + supported edition values are `2015`, `2018`, `2021`, or `2024`, but `chicken` is unknown " .to_string(), ) @@ -1391,7 +1391,7 @@ Caused by: failed to parse the `edition` key Caused by: - this version of Cargo is older than the `2038` edition, and only supports `2015`, `2018`, and `2021` editions. + this version of Cargo is older than the `2038` edition, and only supports `2015`, `2018`, `2021`, and `2024` editions. " .to_string(), ) @@ -3095,3 +3095,40 @@ src/main.rs &[], ); } + +#[cargo_test] +fn versionless_package() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + description = "foo" + "#, + ) + .file("src/main.rs", r#"fn main() { println!("hello"); }"#) + .build(); + + p.cargo("package") + .with_stderr( + "\ +warning: manifest has no license, license-file, documentation, homepage or repository. +See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info. + Packaging foo v0.0.0 ([CWD]) + Verifying foo v0.0.0 ([CWD]) + Compiling foo v0.0.0 ([CWD]/target/package/foo-0.0.0) + Finished dev [unoptimized + debuginfo] target(s) in [..]s + Packaged 4 files, [..]B ([..]B compressed) +", + ) + .run(); + + let f = File::open(&p.root().join("target/package/foo-0.0.0.crate")).unwrap(); + validate_crate_contents( + f, + "foo-0.0.0.crate", + &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"], + &[], + ); +} diff --git a/src/tools/cargo/tests/testsuite/plugins.rs b/src/tools/cargo/tests/testsuite/plugins.rs deleted file mode 100644 index 331ba32e0..000000000 --- a/src/tools/cargo/tests/testsuite/plugins.rs +++ /dev/null @@ -1,421 +0,0 @@ -//! Tests for rustc plugins. - -use cargo_test_support::rustc_host; -use cargo_test_support::{basic_manifest, project}; - -#[cargo_test(nightly, reason = "plugins are unstable")] -fn plugin_to_the_max() { - let foo = project() - .file( - "Cargo.toml", - r#" - [package] - name = "foo" - version = "0.0.1" - authors = [] - - [lib] - name = "foo_lib" - - [dependencies.bar] - path = "../bar" - "#, - ) - .file( - "src/main.rs", - r#" - #![feature(plugin)] - #![plugin(bar)] - extern crate foo_lib; - - fn main() { foo_lib::foo(); } - "#, - ) - .file( - "src/foo_lib.rs", - r#" - #![feature(plugin)] - #![plugin(bar)] - - pub fn foo() {} - "#, - ) - .build(); - let _bar = project() - .at("bar") - .file( - "Cargo.toml", - r#" - [package] - name = "bar" - version = "0.0.1" - authors = [] - - [lib] - name = "bar" - plugin = true - - [dependencies.baz] - path = "../baz" - "#, - ) - .file( - "src/lib.rs", - r#" - #![feature(rustc_private)] - - extern crate baz; - extern crate rustc_driver; - - use rustc_driver::plugin::Registry; - - #[no_mangle] - pub fn __rustc_plugin_registrar(_reg: &mut Registry) { - println!("{}", baz::baz()); - } - "#, - ) - .build(); - let _baz = project() - .at("baz") - .file( - "Cargo.toml", - r#" - [package] - name = "baz" - version = "0.0.1" - authors = [] - - [lib] - name = "baz" - crate_type = ["dylib"] - "#, - ) - .file("src/lib.rs", "pub fn baz() -> i32 { 1 }") - .build(); - - foo.cargo("build").run(); - foo.cargo("doc").run(); -} - -#[cargo_test(nightly, reason = "plugins are unstable")] -fn plugin_with_dynamic_native_dependency() { - let build = project() - .at("builder") - .file( - "Cargo.toml", - r#" - [package] - name = "builder" - version = "0.0.1" - authors = [] - - [lib] - name = "builder" - crate-type = ["dylib"] - "#, - ) - .file("src/lib.rs", "#[no_mangle] pub extern fn foo() {}") - .build(); - - let foo = project() - .file( - "Cargo.toml", - r#" - [package] - name = "foo" - version = "0.0.1" - authors = [] - - [dependencies.bar] - path = "bar" - "#, - ) - .file( - "src/main.rs", - r#" - #![feature(plugin)] - #![plugin(bar)] - - fn main() {} - "#, - ) - .file( - "bar/Cargo.toml", - r#" - [package] - name = "bar" - version = "0.0.1" - authors = [] - build = 'build.rs' - - [lib] - name = "bar" - plugin = true - "#, - ) - .file( - "bar/build.rs", - r#" - use std::env; - use std::fs; - use std::path::PathBuf; - - fn main() { - let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); - let root = PathBuf::from(env::var("BUILDER_ROOT").unwrap()); - let file = format!("{}builder{}", - env::consts::DLL_PREFIX, - env::consts::DLL_SUFFIX); - let src = root.join(&file); - let dst = out_dir.join(&file); - fs::copy(src, dst).unwrap(); - if cfg!(target_env = "msvc") { - fs::copy(root.join("builder.dll.lib"), - out_dir.join("builder.dll.lib")).unwrap(); - } - println!("cargo:rustc-flags=-L {}", out_dir.display()); - } - "#, - ) - .file( - "bar/src/lib.rs", - r#" - #![feature(rustc_private)] - - extern crate rustc_driver; - use rustc_driver::plugin::Registry; - - #[cfg_attr(not(target_env = "msvc"), link(name = "builder"))] - #[cfg_attr(target_env = "msvc", link(name = "builder.dll"))] - extern { fn foo(); } - - #[no_mangle] - pub fn __rustc_plugin_registrar(_reg: &mut Registry) { - unsafe { foo() } - } - "#, - ) - .build(); - - build.cargo("build").run(); - - let root = build.root().join("target").join("debug"); - foo.cargo("build -v").env("BUILDER_ROOT", root).run(); -} - -#[cargo_test] -fn plugin_integration() { - let p = project() - .file( - "Cargo.toml", - r#" - [package] - name = "foo" - version = "0.0.1" - authors = [] - build = "build.rs" - - [lib] - name = "foo" - plugin = true - doctest = false - "#, - ) - .file("build.rs", "fn main() {}") - .file("src/lib.rs", "") - .file("tests/it_works.rs", "") - .build(); - - p.cargo("test -v").run(); -} - -#[cargo_test] -fn doctest_a_plugin() { - let p = project() - .file( - "Cargo.toml", - r#" - [package] - name = "foo" - version = "0.0.1" - authors = [] - - [dependencies] - bar = { path = "bar" } - "#, - ) - .file("src/lib.rs", "#[macro_use] extern crate bar;") - .file( - "bar/Cargo.toml", - r#" - [package] - name = "bar" - version = "0.0.1" - authors = [] - - [lib] - name = "bar" - plugin = true - "#, - ) - .file("bar/src/lib.rs", "pub fn bar() {}") - .build(); - - p.cargo("test -v").run(); -} - -// See #1515 -#[cargo_test] -fn native_plugin_dependency_with_custom_linker() { - let target = rustc_host(); - - let _foo = project() - .file( - "Cargo.toml", - r#" - [package] - name = "foo" - version = "0.0.1" - authors = [] - - [lib] - plugin = true - "#, - ) - .file("src/lib.rs", "") - .build(); - - let bar = project() - .at("bar") - .file( - "Cargo.toml", - r#" - [package] - name = "bar" - version = "0.0.1" - authors = [] - - [dependencies.foo] - path = "../foo" - "#, - ) - .file("src/lib.rs", "") - .file( - ".cargo/config", - &format!( - r#" - [target.{}] - linker = "nonexistent-linker" - "#, - target - ), - ) - .build(); - - bar.cargo("build --verbose") - .with_status(101) - .with_stderr_contains( - "\ -[COMPILING] foo v0.0.1 ([..]) -[RUNNING] `rustc [..] -C linker=nonexistent-linker [..]` -[ERROR] [..]linker[..] -", - ) - .run(); -} - -#[cargo_test(nightly, reason = "requires rustc_private")] -fn panic_abort_plugins() { - let p = project() - .file( - "Cargo.toml", - r#" - [package] - name = "foo" - version = "0.0.1" - authors = [] - - [profile.dev] - panic = 'abort' - - [dependencies] - bar = { path = "bar" } - "#, - ) - .file("src/lib.rs", "") - .file( - "bar/Cargo.toml", - r#" - [package] - name = "bar" - version = "0.0.1" - authors = [] - - [lib] - plugin = true - "#, - ) - .file( - "bar/src/lib.rs", - r#" - #![feature(rustc_private)] - extern crate rustc_ast; - extern crate rustc_driver; - "#, - ) - .build(); - - p.cargo("build").run(); -} - -#[cargo_test(nightly, reason = "requires rustc_private")] -fn shared_panic_abort_plugins() { - let p = project() - .file( - "Cargo.toml", - r#" - [package] - name = "foo" - version = "0.0.1" - authors = [] - - [profile.dev] - panic = 'abort' - - [dependencies] - bar = { path = "bar" } - baz = { path = "baz" } - "#, - ) - .file("src/lib.rs", "extern crate baz;") - .file( - "bar/Cargo.toml", - r#" - [package] - name = "bar" - version = "0.0.1" - authors = [] - - [lib] - plugin = true - - [dependencies] - baz = { path = "../baz" } - "#, - ) - .file( - "bar/src/lib.rs", - r#" - #![feature(rustc_private)] - extern crate rustc_ast; - extern crate rustc_driver; - extern crate baz; - "#, - ) - .file("baz/Cargo.toml", &basic_manifest("baz", "0.0.1")) - .file("baz/src/lib.rs", "") - .build(); - - p.cargo("build -v").run(); -} diff --git a/src/tools/cargo/tests/testsuite/proc_macro.rs b/src/tools/cargo/tests/testsuite/proc_macro.rs index 7d6f6ba86..cabf251a0 100644 --- a/src/tools/cargo/tests/testsuite/proc_macro.rs +++ b/src/tools/cargo/tests/testsuite/proc_macro.rs @@ -202,52 +202,6 @@ fn impl_and_derive() { p.cargo("run").with_stdout("X { success: true }").run(); } -#[cargo_test(nightly, reason = "plugins are unstable")] -fn plugin_and_proc_macro() { - let p = project() - .file( - "Cargo.toml", - r#" - [package] - name = "foo" - version = "0.0.1" - authors = [] - - [lib] - plugin = true - proc-macro = true - "#, - ) - .file( - "src/lib.rs", - r#" - #![feature(rustc_private)] - #![feature(proc_macro, proc_macro_lib)] - - extern crate rustc_driver; - use rustc_driver::plugin::Registry; - - extern crate proc_macro; - use proc_macro::TokenStream; - - #[no_mangle] - pub fn __rustc_plugin_registrar(reg: &mut Registry) {} - - #[proc_macro_derive(Questionable)] - pub fn questionable(input: TokenStream) -> TokenStream { - input - } - "#, - ) - .build(); - - let msg = " `lib.plugin` and `lib.proc-macro` cannot both be `true`"; - p.cargo("check") - .with_status(101) - .with_stderr_contains(msg) - .run(); -} - #[cargo_test] fn proc_macro_doctest() { let foo = project() diff --git a/src/tools/cargo/tests/testsuite/profile_config.rs b/src/tools/cargo/tests/testsuite/profile_config.rs index 143c050f9..710a0d8ef 100644 --- a/src/tools/cargo/tests/testsuite/profile_config.rs +++ b/src/tools/cargo/tests/testsuite/profile_config.rs @@ -1,6 +1,6 @@ //! Tests for profiles defined in config files. -use cargo::util::toml::TomlDebugInfo; +use cargo::util::toml::schema::TomlDebugInfo; use cargo_test_support::paths::CargoPathExt; use cargo_test_support::registry::Package; use cargo_test_support::{basic_lib_manifest, paths, project}; diff --git a/src/tools/cargo/tests/testsuite/profile_targets.rs b/src/tools/cargo/tests/testsuite/profile_targets.rs index f2de169b9..9f00b73f3 100644 --- a/src/tools/cargo/tests/testsuite/profile_targets.rs +++ b/src/tools/cargo/tests/testsuite/profile_targets.rs @@ -667,5 +667,6 @@ fn profile_selection_doc() { [DOCUMENTING] foo [..] [RUNNING] `rustdoc [..]--crate-name foo src/lib.rs [..] [FINISHED] dev [unoptimized + debuginfo] [..] +[GENERATED] [CWD]/target/doc/foo/index.html ").run(); } diff --git a/src/tools/cargo/tests/testsuite/profile_trim_paths.rs b/src/tools/cargo/tests/testsuite/profile_trim_paths.rs new file mode 100644 index 000000000..1d24c159b --- /dev/null +++ b/src/tools/cargo/tests/testsuite/profile_trim_paths.rs @@ -0,0 +1,614 @@ +//! Tests for `-Ztrim-paths`. + +use cargo_test_support::basic_manifest; +use cargo_test_support::git; +use cargo_test_support::paths; +use cargo_test_support::project; +use cargo_test_support::registry::Package; + +#[cargo_test] +fn gated_manifest() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + + [profile.dev] + trim-paths = "macro" + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("check") + .masquerade_as_nightly_cargo(&["-Ztrim-paths"]) + .with_status(101) + .with_stderr_contains( + "\ +[ERROR] failed to parse manifest at `[CWD]/Cargo.toml` + +Caused by: + feature `trim-paths` is required", + ) + .run(); +} + +#[cargo_test] +fn gated_config_toml() { + let p = project() + .file( + ".cargo/config.toml", + r#" + [profile.dev] + trim-paths = "macro" + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("check") + .masquerade_as_nightly_cargo(&["-Ztrim-paths"]) + .with_status(101) + .with_stderr_contains( + "\ +[ERROR] config profile `dev` is not valid (defined in `[CWD]/.cargo/config.toml`) + +Caused by: + feature `trim-paths` is required", + ) + .run(); +} + +#[cargo_test(nightly, reason = "-Zremap-path-scope is unstable")] +fn release_profile_default_to_object() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("build --release --verbose -Ztrim-paths") + .masquerade_as_nightly_cargo(&["-Ztrim-paths"]) + .with_stderr( + "\ +[COMPILING] foo v0.0.1 ([CWD]) +[RUNNING] `rustc [..]\ + -Zremap-path-scope=object \ + --remap-path-prefix=[..]/lib/rustlib/src/rust=/rustc/[..] \ + --remap-path-prefix=[CWD]= [..] +[FINISHED] release [..]", + ) + .run(); +} + +#[cargo_test(nightly, reason = "-Zremap-path-scope is unstable")] +fn one_option() { + let build = |option| { + let p = project() + .file( + "Cargo.toml", + &format!( + r#" + [package] + name = "foo" + version = "0.0.1" + + [profile.dev] + trim-paths = "{option}" + "# + ), + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("build -v -Ztrim-paths") + }; + + for option in ["macro", "diagnostics", "object", "all"] { + build(option) + .masquerade_as_nightly_cargo(&["-Ztrim-paths"]) + .with_stderr(&format!( + "\ +[COMPILING] foo v0.0.1 ([CWD]) +[RUNNING] `rustc [..]\ + -Zremap-path-scope={option} \ + --remap-path-prefix=[..]/lib/rustlib/src/rust=/rustc/[..] \ + --remap-path-prefix=[CWD]= [..] +[FINISHED] dev [..]", + )) + .run(); + } + build("none") + .masquerade_as_nightly_cargo(&["-Ztrim-paths"]) + .with_stderr_does_not_contain("[..]-Zremap-path-scope=[..]") + .with_stderr_does_not_contain("[..]--remap-path-prefix=[..]") + .run(); +} + +#[cargo_test(nightly, reason = "-Zremap-path-scope is unstable")] +fn multiple_options() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + + [profile.dev] + trim-paths = ["diagnostics", "macro", "object"] + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("build --verbose -Ztrim-paths") + .masquerade_as_nightly_cargo(&["-Ztrim-paths"]) + .with_stderr( + "\ +[COMPILING] foo v0.0.1 ([CWD]) +[RUNNING] `rustc [..]\ + -Zremap-path-scope=diagnostics,macro,object \ + --remap-path-prefix=[..]/lib/rustlib/src/rust=/rustc/[..] \ + --remap-path-prefix=[CWD]= [..] +[FINISHED] dev [..]", + ) + .run(); +} + +#[cargo_test(nightly, reason = "-Zremap-path-scope is unstable")] +fn profile_merge_works() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + + [profile.dev] + trim-paths = ["macro"] + + [profile.custom] + inherits = "dev" + trim-paths = ["diagnostics"] + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("build -v -Ztrim-paths --profile custom") + .masquerade_as_nightly_cargo(&["-Ztrim-paths"]) + .with_stderr( + "\ +[COMPILING] foo v0.0.1 ([CWD]) +[RUNNING] `rustc [..]\ + -Zremap-path-scope=diagnostics \ + --remap-path-prefix=[..]/lib/rustlib/src/rust=/rustc/[..] \ + --remap-path-prefix=[CWD]= [..] +[FINISHED] custom [..]", + ) + .run(); +} + +#[cargo_test(nightly, reason = "-Zremap-path-scope is unstable")] +fn registry_dependency() { + Package::new("bar", "0.0.1") + .file("Cargo.toml", &basic_manifest("bar", "0.0.1")) + .file("src/lib.rs", r#"pub fn f() { println!("{}", file!()); }"#) + .publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + + [dependencies] + bar = "0.0.1" + + [profile.dev] + trim-paths = "object" + "#, + ) + .file("src/main.rs", "fn main() { bar::f(); }") + .build(); + + let registry_src = paths::home().join(".cargo/registry/src"); + let pkg_remap = format!("{}/[..]/bar-0.0.1=bar-0.0.1", registry_src.display()); + + p.cargo("run --verbose -Ztrim-paths") + .masquerade_as_nightly_cargo(&["-Ztrim-paths"]) + .with_stdout("bar-0.0.1/src/lib.rs") + .with_stderr(&format!( + "\ +[UPDATING] [..] +[DOWNLOADING] crates ... +[DOWNLOADED] bar v0.0.1 ([..]) +[COMPILING] bar v0.0.1 +[RUNNING] `rustc [..]\ + -Zremap-path-scope=object \ + --remap-path-prefix=[..]/lib/rustlib/src/rust=/rustc/[..] \ + --remap-path-prefix={pkg_remap} [..] +[COMPILING] foo v0.0.1 ([CWD]) +[RUNNING] `rustc [..]\ + -Zremap-path-scope=object \ + --remap-path-prefix=[..]/lib/rustlib/src/rust=/rustc/[..] \ + --remap-path-prefix=[CWD]= [..] +[FINISHED] dev [..] +[RUNNING] `target/debug/foo[EXE]`" + )) + .run(); +} + +#[cargo_test(nightly, reason = "-Zremap-path-scope is unstable")] +fn git_dependency() { + let git_project = git::new("bar", |project| { + project + .file("Cargo.toml", &basic_manifest("bar", "0.0.1")) + .file("src/lib.rs", r#"pub fn f() { println!("{}", file!()); }"#) + }); + let url = git_project.url(); + + let p = project() + .file( + "Cargo.toml", + &format!( + r#" + [package] + name = "foo" + version = "0.0.1" + + [dependencies] + bar = {{ git = "{url}" }} + + [profile.dev] + trim-paths = "object" + "# + ), + ) + .file("src/main.rs", "fn main() { bar::f(); }") + .build(); + + let git_checkouts_src = paths::home().join(".cargo/git/checkouts"); + let pkg_remap = format!("{}/bar-[..]/[..]=bar-0.0.1", git_checkouts_src.display()); + + p.cargo("run --verbose -Ztrim-paths") + .masquerade_as_nightly_cargo(&["-Ztrim-paths"]) + .with_stdout("bar-0.0.1/src/lib.rs") + .with_stderr(&format!( + "\ +[UPDATING] git repository `{url}` +[COMPILING] bar v0.0.1 ({url}[..]) +[RUNNING] `rustc [..]\ + -Zremap-path-scope=object \ + --remap-path-prefix=[..]/lib/rustlib/src/rust=/rustc/[..] \ + --remap-path-prefix={pkg_remap} [..] +[COMPILING] foo v0.0.1 ([CWD]) +[RUNNING] `rustc [..]\ + -Zremap-path-scope=object \ + --remap-path-prefix=[..]/lib/rustlib/src/rust=/rustc/[..] \ + --remap-path-prefix=[CWD]= [..] +[FINISHED] dev [..] +[RUNNING] `target/debug/foo[EXE]`" + )) + .run(); +} + +#[cargo_test(nightly, reason = "-Zremap-path-scope is unstable")] +fn path_dependency() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + + [dependencies] + bar = { path = "cocktail-bar" } + + [profile.dev] + trim-paths = "object" + "#, + ) + .file("src/main.rs", "fn main() { bar::f(); }") + .file("cocktail-bar/Cargo.toml", &basic_manifest("bar", "0.0.1")) + .file( + "cocktail-bar/src/lib.rs", + r#"pub fn f() { println!("{}", file!()); }"#, + ) + .build(); + + p.cargo("run --verbose -Ztrim-paths") + .masquerade_as_nightly_cargo(&["-Ztrim-paths"]) + .with_stdout("cocktail-bar/src/lib.rs") + .with_stderr(&format!( + "\ +[COMPILING] bar v0.0.1 ([..]/cocktail-bar) +[RUNNING] `rustc [..]\ + -Zremap-path-scope=object \ + --remap-path-prefix=[..]/lib/rustlib/src/rust=/rustc/[..] \ + --remap-path-prefix=[CWD]= [..] +[COMPILING] foo v0.0.1 ([CWD]) +[RUNNING] `rustc [..]\ + -Zremap-path-scope=object \ + --remap-path-prefix=[..]/lib/rustlib/src/rust=/rustc/[..] \ + --remap-path-prefix=[CWD]= [..] +[FINISHED] dev [..] +[RUNNING] `target/debug/foo[EXE]`" + )) + .run(); +} + +#[cargo_test(nightly, reason = "-Zremap-path-scope is unstable")] +fn path_dependency_outside_workspace() { + let bar = project() + .at("bar") + .file("Cargo.toml", &basic_manifest("bar", "0.0.1")) + .file("src/lib.rs", r#"pub fn f() { println!("{}", file!()); }"#) + .build(); + let bar_path = bar.url().to_file_path().unwrap(); + let bar_path = bar_path.display(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + + [dependencies] + bar = { path = "../bar" } + + [profile.dev] + trim-paths = "object" + "#, + ) + .file("src/main.rs", "fn main() { bar::f(); }") + .build(); + + p.cargo("run --verbose -Ztrim-paths") + .masquerade_as_nightly_cargo(&["-Ztrim-paths"]) + .with_stdout("bar-0.0.1/src/lib.rs") + .with_stderr(&format!( + "\ +[COMPILING] bar v0.0.1 ([..]/bar) +[RUNNING] `rustc [..]\ + -Zremap-path-scope=object \ + --remap-path-prefix=[..]/lib/rustlib/src/rust=/rustc/[..] \ + --remap-path-prefix={bar_path}=bar-0.0.1 [..] +[COMPILING] foo v0.0.1 ([CWD]) +[RUNNING] `rustc [..]\ + -Zremap-path-scope=object \ + --remap-path-prefix=[..]/lib/rustlib/src/rust=/rustc/[..] \ + --remap-path-prefix=[CWD]= [..] +[FINISHED] dev [..] +[RUNNING] `target/debug/foo[EXE]`" + )) + .run(); +} + +#[cargo_test(nightly, reason = "-Zremap-path-scope is unstable")] +fn diagnostics_works() { + Package::new("bar", "0.0.1") + .file("Cargo.toml", &basic_manifest("bar", "0.0.1")) + .file("src/lib.rs", r#"pub fn f() { let unused = 0; }"#) + .publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + + [dependencies] + bar = "0.0.1" + + [profile.dev] + trim-paths = "diagnostics" + "#, + ) + .file("src/lib.rs", "") + .build(); + + let registry_src = paths::home().join(".cargo/registry/src"); + let registry_src = registry_src.display(); + let pkg_remap = format!("{registry_src}/[..]/bar-0.0.1=bar-0.0.1"); + + p.cargo("build -vv -Ztrim-paths") + .masquerade_as_nightly_cargo(&["-Ztrim-paths"]) + .with_stderr_line_without( + &["[..]bar-0.0.1/src/lib.rs:1[..]"], + &[&format!("{registry_src}")], + ) + .with_stderr_contains("[..]unused_variables[..]") + .with_stderr_contains(&format!( + "\ +[RUNNING] [..]rustc [..]\ + -Zremap-path-scope=diagnostics \ + --remap-path-prefix=[..]/lib/rustlib/src/rust=/rustc/[..] \ + --remap-path-prefix={pkg_remap} [..]", + )) + .with_stderr_contains( + "\ +[RUNNING] [..]rustc [..]\ + -Zremap-path-scope=diagnostics \ + --remap-path-prefix=[..]/lib/rustlib/src/rust=/rustc/[..] \ + --remap-path-prefix=[CWD]= [..]", + ) + .run(); +} + +#[cfg(target_os = "linux")] +#[cargo_test(requires_readelf, nightly, reason = "-Zremap-path-scope is unstable")] +fn object_works() { + use std::os::unix::ffi::OsStrExt; + + let run_readelf = |path| { + std::process::Command::new("readelf") + .arg("-wi") + .arg(path) + .output() + .expect("readelf works") + }; + + let registry_src = paths::home().join(".cargo/registry/src"); + let pkg_remap = format!("{}/[..]/bar-0.0.1=bar-0.0.1", registry_src.display()); + let rust_src = "/lib/rustc/src/rust".as_bytes(); + let registry_src_bytes = registry_src.as_os_str().as_bytes(); + + Package::new("bar", "0.0.1") + .file("Cargo.toml", &basic_manifest("bar", "0.0.1")) + .file("src/lib.rs", r#"pub fn f() { println!("{}", file!()); }"#) + .publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + + [dependencies] + bar = "0.0.1" + "#, + ) + .file("src/main.rs", "fn main() { bar::f(); }") + .build(); + + let pkg_root = p.root(); + let pkg_root = pkg_root.as_os_str().as_bytes(); + + p.cargo("build").run(); + + let bin_path = p.bin("foo"); + assert!(bin_path.is_file()); + let stdout = run_readelf(bin_path).stdout; + // TODO: re-enable this check when rustc bootstrap disables remapping + // <https://github.com/rust-lang/cargo/pull/12625#discussion_r1371714791> + // assert!(memchr::memmem::find(&stdout, rust_src).is_some()); + assert!(memchr::memmem::find(&stdout, registry_src_bytes).is_some()); + assert!(memchr::memmem::find(&stdout, pkg_root).is_some()); + + p.cargo("clean").run(); + + p.change_file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + + [dependencies] + bar = "0.0.1" + + [profile.dev] + trim-paths = "object" + "#, + ); + + p.cargo("build --verbose -Ztrim-paths") + .masquerade_as_nightly_cargo(&["-Ztrim-paths"]) + .with_stderr(&format!( + "\ +[COMPILING] bar v0.0.1 +[RUNNING] `rustc [..]\ + -Zremap-path-scope=object \ + --remap-path-prefix=[..]/lib/rustlib/src/rust=/rustc/[..] \ + --remap-path-prefix={pkg_remap} [..] +[COMPILING] foo v0.0.1 ([CWD]) +[RUNNING] `rustc [..]\ + -Zremap-path-scope=object \ + --remap-path-prefix=[..]/lib/rustlib/src/rust=/rustc/[..] \ + --remap-path-prefix=[CWD]= [..] +[FINISHED] dev [..]", + )) + .run(); + + let bin_path = p.bin("foo"); + assert!(bin_path.is_file()); + let stdout = run_readelf(bin_path).stdout; + assert!(memchr::memmem::find(&stdout, rust_src).is_none()); + assert!(memchr::memmem::find(&stdout, registry_src_bytes).is_none()); + assert!(memchr::memmem::find(&stdout, pkg_root).is_none()); +} + +// TODO: might want to move to test/testsuite/build_script.rs once stabilized. +#[cargo_test(nightly, reason = "-Zremap-path-scope is unstable")] +fn custom_build_env_var_trim_paths() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + "#, + ) + .file("src/lib.rs", "") + .file("build.rs", "") + .build(); + + let test_cases = [ + ("[]", "none"), + ("\"all\"", "all"), + ("\"diagnostics\"", "diagnostics"), + ("\"macro\"", "macro"), + ("\"none\"", "none"), + ("\"object\"", "object"), + ("false", "none"), + ("true", "all"), + ( + r#"["diagnostics", "macro", "object"]"#, + "diagnostics,macro,object", + ), + ]; + + for (opts, expected) in test_cases { + p.change_file( + "Cargo.toml", + &format!( + r#" + [package] + name = "foo" + version = "0.0.1" + + [profile.dev] + trim-paths = {opts} + "# + ), + ); + + p.change_file( + "build.rs", + &format!( + r#" + fn main() {{ + assert_eq!( + std::env::var("CARGO_TRIM_PATHS").unwrap().as_str(), + "{expected}", + ); + }} + "# + ), + ); + + p.cargo("build -Ztrim-paths") + .masquerade_as_nightly_cargo(&["-Ztrim-paths"]) + .run(); + } +} diff --git a/src/tools/cargo/tests/testsuite/pub_priv.rs b/src/tools/cargo/tests/testsuite/pub_priv.rs index 83c6a49f8..b2160e0fa 100644 --- a/src/tools/cargo/tests/testsuite/pub_priv.rs +++ b/src/tools/cargo/tests/testsuite/pub_priv.rs @@ -197,3 +197,52 @@ Caused by: ) .run() } + +#[cargo_test(nightly, reason = "exported_private_dependencies lint is unstable")] +fn workspace_dep_made_public() { + Package::new("foo1", "0.1.0") + .file("src/lib.rs", "pub struct FromFoo;") + .publish(); + Package::new("foo2", "0.1.0") + .file("src/lib.rs", "pub struct FromFoo;") + .publish(); + Package::new("foo3", "0.1.0") + .file("src/lib.rs", "pub struct FromFoo;") + .publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["public-dependency"] + + [package] + name = "foo" + version = "0.0.1" + + [workspace.dependencies] + foo1 = "0.1.0" + foo2 = { version = "0.1.0", public = true } + foo3 = { version = "0.1.0", public = false } + + [dependencies] + foo1 = { workspace = true, public = true } + foo2 = { workspace = true } + foo3 = { workspace = true, public = true } + "#, + ) + .file( + "src/lib.rs", + " + #![deny(exported_private_dependencies)] + pub fn use_priv1(_: foo1::FromFoo) {} + pub fn use_priv2(_: foo2::FromFoo) {} + pub fn use_priv3(_: foo3::FromFoo) {} + ", + ) + .build(); + + p.cargo("check") + .masquerade_as_nightly_cargo(&["public-dependency"]) + .run() +} diff --git a/src/tools/cargo/tests/testsuite/publish.rs b/src/tools/cargo/tests/testsuite/publish.rs index 67569bf3b..5d29ac88a 100644 --- a/src/tools/cargo/tests/testsuite/publish.rs +++ b/src/tools/cargo/tests/testsuite/publish.rs @@ -420,7 +420,7 @@ fn unpublishable_crate() { .with_stderr( "\ [ERROR] `foo` cannot be published. -`package.publish` is set to `false` or an empty list in Cargo.toml and prevents publishing. +`package.publish` must be set to `true` or a non-empty list in Cargo.toml to publish. ", ) .run(); @@ -794,7 +794,7 @@ fn publish_empty_list() { .with_stderr( "\ [ERROR] `foo` cannot be published. -`package.publish` is set to `false` or an empty list in Cargo.toml and prevents publishing. +`package.publish` must be set to `true` or a non-empty list in Cargo.toml to publish. ", ) .run(); @@ -1020,7 +1020,7 @@ fn block_publish_no_registry() { .with_stderr( "\ [ERROR] `foo` cannot be published. -`package.publish` is set to `false` or an empty list in Cargo.toml and prevents publishing. +`package.publish` must be set to `true` or a non-empty list in Cargo.toml to publish. ", ) .run(); @@ -3004,3 +3004,32 @@ Caused by: .with_status(101) .run(); } + +#[cargo_test] +fn versionless_package() { + // Use local registry for faster test times since no publish will occur + let registry = registry::init(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + description = "foo" + "#, + ) + .file("src/main.rs", r#"fn main() { println!("hello"); }"#) + .build(); + + p.cargo("publish") + .replace_crates_io(registry.index_url()) + .with_status(101) + .with_stderr( + "\ +error: `foo` cannot be published. +`package.publish` must be set to `true` or a non-empty list in Cargo.toml to publish. +", + ) + .run(); +} diff --git a/src/tools/cargo/tests/testsuite/registry.rs b/src/tools/cargo/tests/testsuite/registry.rs index f485180c9..b5dff2746 100644 --- a/src/tools/cargo/tests/testsuite/registry.rs +++ b/src/tools/cargo/tests/testsuite/registry.rs @@ -3600,4 +3600,55 @@ fn differ_only_by_metadata() { ", ) .run(); + + Package::new("baz", "0.0.1+d").publish(); + + p.cargo("clean").run(); + p.cargo("check") + .with_stderr_contains("[CHECKING] baz v0.0.1+b") + .run(); +} + +#[cargo_test] +fn differ_only_by_metadata_with_lockfile() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies] + baz = "=0.0.1" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + Package::new("baz", "0.0.1+a").publish(); + Package::new("baz", "0.0.1+b").publish(); + Package::new("baz", "0.0.1+c").publish(); + + p.cargo("update --package baz --precise 0.0.1+b") + .with_stderr( + "\ +[UPDATING] [..] index +[..] baz v0.0.1+c -> v0.0.1+b +", + ) + .run(); + + p.cargo("check") + .with_stderr( + "\ +[DOWNLOADING] crates ... +[DOWNLOADED] [..] v0.0.1+b (registry `dummy-registry`) +[CHECKING] baz v0.0.1+b +[CHECKING] foo v0.0.1 ([CWD]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s +", + ) + .run(); } diff --git a/src/tools/cargo/tests/testsuite/run.rs b/src/tools/cargo/tests/testsuite/run.rs index c58c239ac..c7ddd5d9e 100644 --- a/src/tools/cargo/tests/testsuite/run.rs +++ b/src/tools/cargo/tests/testsuite/run.rs @@ -50,7 +50,7 @@ error: unexpected argument '--silent' found tip: a similar argument exists: '--quiet' -Usage: cargo[EXE] run [OPTIONS] [args]... +Usage: cargo[EXE] run [OPTIONS] [ARGS]... For more information, try '--help'. ", @@ -65,7 +65,7 @@ error: unexpected argument '--silent' found tip: a similar argument exists: '--quiet' -Usage: cargo[EXE] run [OPTIONS] [args]... +Usage: cargo[EXE] run [OPTIONS] [ARGS]... For more information, try '--help'. ", diff --git a/src/tools/cargo/tests/testsuite/rustdoc.rs b/src/tools/cargo/tests/testsuite/rustdoc.rs index 5650f3e0a..7ef768a80 100644 --- a/src/tools/cargo/tests/testsuite/rustdoc.rs +++ b/src/tools/cargo/tests/testsuite/rustdoc.rs @@ -15,6 +15,7 @@ fn rustdoc_simple() { [..] \ -L dependency=[CWD]/target/debug/deps [..]` [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/foo/index.html ", ) .run(); @@ -35,6 +36,7 @@ fn rustdoc_args() { -C metadata=[..] \ -L dependency=[CWD]/target/debug/deps [..]` [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/foo/index.html ", ) .run(); @@ -88,6 +90,7 @@ fn rustdoc_foo_with_bar_dependency() { -L dependency=[CWD]/target/debug/deps \ --extern [..]` [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/foo/index.html ", ) .run(); @@ -127,6 +130,7 @@ fn rustdoc_only_bar_dependency() { -C metadata=[..] \ -L dependency=[CWD]/target/debug/deps [..]` [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/bar/index.html ", ) .run(); @@ -150,6 +154,7 @@ fn rustdoc_same_name_documents_lib() { -C metadata=[..] \ -L dependency=[CWD]/target/debug/deps [..]` [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/foo/index.html ", ) .run(); @@ -225,7 +230,8 @@ fn rustdoc_target() { [..] \ -L dependency=[CWD]/target/{target}/debug/deps \ -L dependency=[CWD]/target/debug/deps[..]` -[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]", +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/[..]/doc/foo/index.html", target = cross_compile::alternate() )) .run(); diff --git a/src/tools/cargo/tests/testsuite/rustdocflags.rs b/src/tools/cargo/tests/testsuite/rustdocflags.rs index c37d5a826..e7c2aa263 100644 --- a/src/tools/cargo/tests/testsuite/rustdocflags.rs +++ b/src/tools/cargo/tests/testsuite/rustdocflags.rs @@ -48,7 +48,10 @@ fn rerun() { p.cargo("doc").env("RUSTDOCFLAGS", "--cfg=foo").run(); p.cargo("doc") .env("RUSTDOCFLAGS", "--cfg=foo") - .with_stderr("[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]") + .with_stderr( + "[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/foo/index.html", + ) .run(); p.cargo("doc") .env("RUSTDOCFLAGS", "--cfg=bar") @@ -56,6 +59,7 @@ fn rerun() { "\ [DOCUMENTING] foo v0.0.1 ([..]) [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[GENERATED] [CWD]/target/doc/foo/index.html ", ) .run(); diff --git a/src/tools/cargo/tests/testsuite/script.rs b/src/tools/cargo/tests/testsuite/script.rs index 96f3a5eb4..0b1e5a6b9 100644 --- a/src/tools/cargo/tests/testsuite/script.rs +++ b/src/tools/cargo/tests/testsuite/script.rs @@ -108,6 +108,7 @@ error: no such command: `echo` <tab>Did you mean `bench`? <tab>View all installed commands with `cargo --list` +<tab>Find a package to install `echo` with `cargo search cargo-echo` ", ) .run(); diff --git a/src/tools/cargo/tests/testsuite/search.rs b/src/tools/cargo/tests/testsuite/search.rs index 4c3155c8f..c76397ac7 100644 --- a/src/tools/cargo/tests/testsuite/search.rs +++ b/src/tools/cargo/tests/testsuite/search.rs @@ -1,5 +1,6 @@ //! Tests for the `cargo search` command. +use cargo::util::cache_lock::CacheLockMode; use cargo_test_support::cargo_process; use cargo_test_support::paths; use cargo_test_support::registry::{RegistryBuilder, Response}; @@ -100,7 +101,9 @@ fn not_update() { paths::root(), paths::home().join(".cargo"), ); - let lock = cfg.acquire_package_cache_lock().unwrap(); + let lock = cfg + .acquire_package_cache_lock(CacheLockMode::DownloadExclusive) + .unwrap(); let mut regsrc = RegistrySource::remote(sid, &HashSet::new(), &cfg).unwrap(); regsrc.invalidate_cache(); regsrc.block_until_ready().unwrap(); diff --git a/src/tools/cargo/tests/testsuite/update.rs b/src/tools/cargo/tests/testsuite/update.rs index fe1d86bd7..e636435b0 100644 --- a/src/tools/cargo/tests/testsuite/update.rs +++ b/src/tools/cargo/tests/testsuite/update.rs @@ -392,6 +392,104 @@ fn update_precise() { } #[cargo_test] +fn update_precise_mismatched() { + Package::new("serde", "1.2.0").publish(); + Package::new("serde", "1.2.1").publish(); + Package::new("serde", "1.6.0").publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "bar" + version = "0.0.1" + authors = [] + + [dependencies] + serde = "~1.2" + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("check").run(); + + // `1.6.0` does not match `"~1.2"` + p.cargo("update serde:1.2 --precise 1.6.0") + .with_stderr( + "\ +[UPDATING] `[..]` index +[ERROR] failed to select a version for the requirement `serde = \"~1.2\"` +candidate versions found which didn't match: 1.6.0 +location searched: `[..]` index (which is replacing registry `crates-io`) +required by package `bar v0.0.1 ([..]/foo)` +perhaps a crate was updated and forgotten to be re-vendored? +", + ) + .with_status(101) + .run(); + + // `1.9.0` does not exist + p.cargo("update serde:1.2 --precise 1.9.0") + // This terrible error message has been the same for a long time. A fix is more than welcome! + .with_stderr( + "\ +[UPDATING] `[..]` index +[ERROR] no matching package named `serde` found +location searched: registry `crates-io` +required by package `bar v0.0.1 ([..]/foo)` +", + ) + .with_status(101) + .run(); +} + +#[cargo_test] +fn update_precise_build_metadata() { + Package::new("serde", "0.0.1+first").publish(); + Package::new("serde", "0.0.1+second").publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.0" + + [dependencies] + serde = "0.0.1" + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("generate-lockfile").run(); + p.cargo("update serde --precise 0.0.1+first").run(); + + p.cargo("update serde --precise 0.0.1+second") + .with_stderr( + "\ +[UPDATING] `[..]` index +[UPDATING] serde v0.0.1+first -> v0.0.1+second +", + ) + .run(); + + // This is not considered "Downgrading". Build metadata are not assumed to + // be ordered. + p.cargo("update serde --precise 0.0.1+first") + .with_stderr( + "\ +[UPDATING] `[..]` index +[UPDATING] serde v0.0.1+second -> v0.0.1+first +", + ) + .run(); +} + +#[cargo_test] fn update_precise_do_not_force_update_deps() { Package::new("log", "0.1.0").publish(); Package::new("serde", "0.2.1").dep("log", "0.1").publish(); diff --git a/src/tools/cargo/tests/testsuite/version.rs b/src/tools/cargo/tests/testsuite/version.rs index f880c75a6..110e61003 100644 --- a/src/tools/cargo/tests/testsuite/version.rs +++ b/src/tools/cargo/tests/testsuite/version.rs @@ -13,6 +13,10 @@ fn simple() { p.cargo("--version") .with_stdout(&format!("cargo {}\n", cargo::version())) .run(); + + p.cargo("-V") + .with_stdout(&format!("cargo {}\n", cargo::version())) + .run(); } #[cargo_test] diff --git a/src/tools/cargo/tests/testsuite/warn_on_failure.rs b/src/tools/cargo/tests/testsuite/warn_on_failure.rs index 19cb01813..f2c2bb071 100644 --- a/src/tools/cargo/tests/testsuite/warn_on_failure.rs +++ b/src/tools/cargo/tests/testsuite/warn_on_failure.rs @@ -105,7 +105,7 @@ fn warning_on_lib_failure() { .with_stderr_contains("[UPDATING] `[..]` index") .with_stderr_contains("[DOWNLOADED] bar v0.0.1 ([..])") .with_stderr_contains("[COMPILING] bar v0.0.1") - .with_stderr_contains(&format!("[WARNING] {}", WARNING1)) - .with_stderr_contains(&format!("[WARNING] {}", WARNING2)) + .with_stderr_contains(&format!("[WARNING] bar@0.0.1: {}", WARNING1)) + .with_stderr_contains(&format!("[WARNING] bar@0.0.1: {}", WARNING2)) .run(); } diff --git a/src/tools/cargo/tests/testsuite/workspaces.rs b/src/tools/cargo/tests/testsuite/workspaces.rs index 4f8997b38..94b5142f4 100644 --- a/src/tools/cargo/tests/testsuite/workspaces.rs +++ b/src/tools/cargo/tests/testsuite/workspaces.rs @@ -1046,7 +1046,7 @@ fn members_include_path_deps() { } #[cargo_test] -fn new_warns_you_this_will_not_work() { +fn new_creates_members_list() { let p = project() .file( "Cargo.toml", @@ -1063,20 +1063,7 @@ fn new_warns_you_this_will_not_work() { let p = p.build(); p.cargo("new --lib bar") - .with_stderr( - "\ -warning: compiling this new package may not work due to invalid workspace configuration - -current package believes it's in a workspace when it's not: -current: [..] -workspace: [..] - -this may be fixable by ensuring that this crate is depended on by the workspace \ -root: [..] -[..] -[CREATED] library `bar` package -", - ) + .with_stderr(" Created library `bar` package") .run(); } diff --git a/src/tools/cargo/triagebot.toml b/src/tools/cargo/triagebot.toml index c92b4ce8c..cdf1090a1 100644 --- a/src/tools/cargo/triagebot.toml +++ b/src/tools/cargo/triagebot.toml @@ -36,6 +36,15 @@ warn_non_default_branch = true [assign.owners] "*" = ["@ehuss", "@epage", "@weihanglo"] + +[review-submitted] +reviewed_label = "S-waiting-on-author" +review_labels = ["S-waiting-on-review"] + +[review-requested] +remove_labels = ["S-waiting-on-author"] +add_labels = ["S-waiting-on-review"] + [autolabel."A-build-execution"] trigger_files = [ "src/cargo/core/compiler/compilation.rs", @@ -192,7 +201,6 @@ trigger_files = ["src/cargo/util/auth/"] trigger_files = [ "crates/semver-check", "src/cargo/util/semver_ext.rs", - "src/cargo/util/to_semver.rs", ] [autolabel."A-source-replacement"] |