diff --git a/Cargo.lock b/Cargo.lock index fe16465f1d59..97e4595d6bd7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -368,7 +368,7 @@ dependencies = [ [[package]] name = "cargo-credential-macos-keychain" -version = "0.3.0" +version = "0.3.1" dependencies = [ "cargo-credential", "security-framework", diff --git a/crates/resolver-tests/src/lib.rs b/crates/resolver-tests/src/lib.rs index 12f6171b1202..9bdeb867415e 100644 --- a/crates/resolver-tests/src/lib.rs +++ b/crates/resolver-tests/src/lib.rs @@ -17,7 +17,7 @@ use cargo::core::Resolve; use cargo::core::{Dependency, PackageId, Registry, Summary}; use cargo::core::{GitReference, SourceId}; use cargo::sources::source::QueryKind; -use cargo::util::{CargoResult, Config, Graph, IntoUrl, PartialVersion}; +use cargo::util::{CargoResult, Config, Graph, IntoUrl, RustVersion}; use proptest::collection::{btree_map, vec}; use proptest::prelude::*; @@ -164,7 +164,7 @@ pub fn resolve_with_config_raw( // we found a case that causes a panic and did not use all of the input. // lets print the part of the input that was used for minimization. eprintln!( - "{:?}", + "Part used befor drop: {:?}", PrettyPrintRegistry( self.list .iter() @@ -185,7 +185,7 @@ pub fn resolve_with_config_raw( deps, &BTreeMap::new(), None::<&String>, - None::, + None::, ) .unwrap(); let opts = ResolveOpts::everything(); @@ -588,7 +588,7 @@ pub fn pkg_dep(name: T, dep: Vec) -> Summary { dep, &BTreeMap::new(), link, - None::, + None::, ) .unwrap() } @@ -616,7 +616,7 @@ pub fn pkg_loc(name: &str, loc: &str) -> Summary { Vec::new(), &BTreeMap::new(), link, - None::, + None::, ) .unwrap() } @@ -630,7 +630,7 @@ pub fn remove_dep(sum: &Summary, ind: usize) -> Summary { deps, &BTreeMap::new(), sum.links().map(|a| a.as_str()), - None::, + None::, ) .unwrap() } diff --git a/crates/resolver-tests/tests/resolve.rs b/crates/resolver-tests/tests/resolve.rs index 02486bfb5db3..dd21502d8fd1 100644 --- a/crates/resolver-tests/tests/resolve.rs +++ b/crates/resolver-tests/tests/resolve.rs @@ -1562,3 +1562,36 @@ package `A v0.0.0 (registry `https://example.com/`)` ... which satisfies dependency `C = \"*\"` of package `A v0.0.0 (registry `https://example.com/`)`\ ", error.to_string()); } + +#[test] +fn shortest_path_in_error_message() { + let input = vec![ + pkg!(("F", "0.1.2")), + pkg!(("F", "0.1.1") => [dep("bad"),]), + pkg!(("F", "0.1.0") => [dep("bad"),]), + pkg!("E" => [dep_req("F", "^0.1.2"),]), + pkg!("D" => [dep_req("F", "^0.1.2"),]), + pkg!("C" => [dep("D"),]), + pkg!("A" => [dep("C"),dep("E"),dep_req("F", "<=0.1.1"),]), + ]; + let error = resolve(vec![dep("A")], ®istry(input)).unwrap_err(); + println!("{}", error); + assert_eq!( + "\ +failed to select a version for `F`. + ... required by package `A v1.0.0 (registry `https://example.com/`)` + ... which satisfies dependency `A = \"*\"` of package `root v1.0.0 (registry `https://example.com/`)` +versions that meet the requirements `<=0.1.1` are: 0.1.1, 0.1.0 + +all possible versions conflict with previously selected packages. + + previously selected package `F v0.1.2 (registry `https://example.com/`)` + ... which satisfies dependency `F = \"^0.1.2\"` of package `E v1.0.0 (registry `https://example.com/`)` + ... which satisfies dependency `E = \"*\"` of package `A v1.0.0 (registry `https://example.com/`)` + ... which satisfies dependency `A = \"*\"` of package `root v1.0.0 (registry `https://example.com/`)` + +failed to select a version for `F` which could resolve this conflict\ + ", + error.to_string() + ); +} diff --git a/credential/cargo-credential-1password/README.md b/credential/cargo-credential-1password/README.md index 7cc15e05b132..3648efe4bda6 100644 --- a/credential/cargo-credential-1password/README.md +++ b/credential/cargo-credential-1password/README.md @@ -1,7 +1,18 @@ # cargo-credential-1password -This is the implementation for the Cargo credential helper for [1password]. -See the [credential-process] documentation for how to use this. +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. [1password]: https://1password.com/ -[credential-process]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#credential-process +[credential provider]: https://doc.rust-lang.org/nightly/cargo/reference/registry-authentication.html diff --git a/credential/cargo-credential-libsecret/README.md b/credential/cargo-credential-libsecret/README.md index f169323e0615..aaba2887f41f 100644 --- a/credential/cargo-credential-libsecret/README.md +++ b/credential/cargo-credential-libsecret/README.md @@ -1,7 +1,9 @@ # cargo-credential-libsecret This is the implementation for the Cargo credential helper for [GNOME libsecret]. -See the [credential-process] documentation for how to use this. +See the [credential-provider] documentation for how to use this. + +This credential provider is built-in to cargo as `cargo:libsecret`. [GNOME libsecret]: https://wiki.gnome.org/Projects/Libsecret -[credential-process]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#credential-process +[credential-provider]: https://doc.rust-lang.org/nightly/cargo/reference/registry-authentication.html diff --git a/credential/cargo-credential-macos-keychain/Cargo.toml b/credential/cargo-credential-macos-keychain/Cargo.toml index 342c771b5e6f..172e9c10b6a4 100644 --- a/credential/cargo-credential-macos-keychain/Cargo.toml +++ b/credential/cargo-credential-macos-keychain/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo-credential-macos-keychain" -version = "0.3.0" +version = "0.3.1" edition.workspace = true license.workspace = true repository = "https://github.com/rust-lang/cargo" diff --git a/credential/cargo-credential-macos-keychain/README.md b/credential/cargo-credential-macos-keychain/README.md index 554116b55728..f5efe496b8fa 100644 --- a/credential/cargo-credential-macos-keychain/README.md +++ b/credential/cargo-credential-macos-keychain/README.md @@ -1,7 +1,10 @@ # cargo-credential-macos-keychain This is the implementation for the Cargo credential helper for [macOS Keychain]. -See the [credential-process] documentation for how to use this. +See the [credential-provider] documentation for how to use this. + +This credential provider is built-in to cargo as `cargo:macos-keychain`. [macOS Keychain]: https://support.apple.com/guide/keychain-access/welcome/mac -[credential-process]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#credential-process +[credential-provider]: https://doc.rust-lang.org/nightly/cargo/reference/registry-authentication.html + diff --git a/credential/cargo-credential-wincred/README.md b/credential/cargo-credential-wincred/README.md index 8c8d1878924d..1995e9d76cf4 100644 --- a/credential/cargo-credential-wincred/README.md +++ b/credential/cargo-credential-wincred/README.md @@ -1,7 +1,9 @@ # cargo-credential-wincred This is the implementation for the Cargo credential helper for [Windows Credential Manager]. -See the [credential-process] documentation for how to use this. +See the [credential-provider] documentation for how to use this. + +This credential provider is built-in to cargo as `cargo:wincred`. [Windows Credential Manager]: https://support.microsoft.com/en-us/windows/accessing-credential-manager-1b5c916a-6a16-889f-8581-fc16e8165ac0 -[credential-process]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#credential-process +[credential-provider]: https://doc.rust-lang.org/nightly/cargo/reference/registry-authentication.html diff --git a/credential/cargo-credential/README.md b/credential/cargo-credential/README.md index 2311f3e57f55..d87d41bb8c8e 100644 --- a/credential/cargo-credential/README.md +++ b/credential/cargo-credential/README.md @@ -5,7 +5,7 @@ provides an interface to store tokens for authorizing access to a registry such as https://crates.io/. Documentation about credential processes may be found at -https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#credential-process +https://doc.rust-lang.org/nightly/cargo/reference/credential-provider-protocol.html Example implementations may be found at https://github.com/rust-lang/cargo/tree/master/credential diff --git a/publish.py b/publish.py index 13077a69b866..87ea0e89657c 100755 --- a/publish.py +++ b/publish.py @@ -11,12 +11,16 @@ import os import re import subprocess -import time import urllib.request from urllib.error import HTTPError TO_PUBLISH = [ + 'credential/cargo-credential', + 'credential/cargo-credential-libsecret', + 'credential/cargo-credential-wincred', + 'credential/cargo-credential-1password', + 'credential/cargo-credential-macos-keychain', 'crates/cargo-platform', 'crates/cargo-util', 'crates/crates-io', @@ -47,13 +51,9 @@ def maybe_publish(path): def main(): print('Starting publish...') - for i, path in enumerate(TO_PUBLISH): - if maybe_publish(path): - if i < len(TO_PUBLISH)-1: - # Sleep to allow the index to update. This should probably - # check that the index is updated, or use a retry loop - # instead. - time.sleep(5) + for path in TO_PUBLISH: + maybe_publish(path) + print('Publish complete!') diff --git a/src/bin/cargo/commands/init.rs b/src/bin/cargo/commands/init.rs index d699c2c49072..48409d827169 100644 --- a/src/bin/cargo/commands/init.rs +++ b/src/bin/cargo/commands/init.rs @@ -7,7 +7,7 @@ pub fn cli() -> Command { .about("Create a new cargo package in an existing directory") .arg(Arg::new("path").action(ArgAction::Set).default_value(".")) .arg_new_opts() - .arg(opt("registry", "Registry to use").value_name("REGISTRY")) + .arg_registry("Registry to use") .arg_quiet() .after_help(color_print::cstr!( "Run `cargo help init` for more detailed information.\n" diff --git a/src/bin/cargo/commands/install.rs b/src/bin/cargo/commands/install.rs index e7907f449f5d..010a4f48385d 100644 --- a/src/bin/cargo/commands/install.rs +++ b/src/bin/cargo/commands/install.rs @@ -154,10 +154,11 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult { } else if krates.is_empty() { from_cwd = true; SourceId::for_path(config.cwd())? - } else if let Some(index) = args.get_one::("index") { - SourceId::for_registry(&index.into_url()?)? - } else if let Some(registry) = args.registry(config)? { - SourceId::alt_registry(config, ®istry)? + } else if let Some(reg_or_index) = args.registry_or_index(config)? { + match reg_or_index { + ops::RegistryOrIndex::Registry(r) => SourceId::alt_registry(config, &r)?, + ops::RegistryOrIndex::Index(url) => SourceId::for_registry(&url)?, + } } else { SourceId::crates_io(config)? }; diff --git a/src/bin/cargo/commands/login.rs b/src/bin/cargo/commands/login.rs index 262e3d6b05fb..118b03310059 100644 --- a/src/bin/cargo/commands/login.rs +++ b/src/bin/cargo/commands/login.rs @@ -1,15 +1,16 @@ -use crate::command_prelude::*; - use cargo::ops; +use cargo::ops::RegistryOrIndex; + +use crate::command_prelude::*; pub fn cli() -> Command { subcommand("login") .about("Log in to a registry.") .arg(Arg::new("token").action(ArgAction::Set)) - .arg(opt("registry", "Registry to use").value_name("REGISTRY")) + .arg_registry("Registry to use") .arg( Arg::new("args") - .help("Arguments for the credential provider (unstable)") + .help("Additional arguments for the credential provider") .num_args(0..) .last(true), ) @@ -20,7 +21,12 @@ pub fn cli() -> Command { } pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult { - let registry = args.registry(config)?; + let reg = args.registry_or_index(config)?; + assert!( + !matches!(reg, Some(RegistryOrIndex::Index(..))), + "must not be index URL" + ); + let extra_args = args .get_many::("args") .unwrap_or_default() @@ -29,7 +35,7 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult { ops::registry_login( config, args.get_one::("token").map(|s| s.as_str().into()), - registry.as_deref(), + reg.as_ref(), &extra_args, )?; Ok(()) diff --git a/src/bin/cargo/commands/logout.rs b/src/bin/cargo/commands/logout.rs index a4fae5bd614f..e7bacca863e2 100644 --- a/src/bin/cargo/commands/logout.rs +++ b/src/bin/cargo/commands/logout.rs @@ -1,10 +1,12 @@ -use crate::command_prelude::*; use cargo::ops; +use cargo::ops::RegistryOrIndex; + +use crate::command_prelude::*; pub fn cli() -> Command { subcommand("logout") .about("Remove an API token from the registry locally") - .arg(opt("registry", "Registry to use").value_name("REGISTRY")) + .arg_registry("Registry to use") .arg_quiet() .after_help(color_print::cstr!( "Run `cargo help logout` for more detailed information.\n" @@ -12,7 +14,12 @@ pub fn cli() -> Command { } pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult { - let registry = args.registry(config)?; - ops::registry_logout(config, registry.as_deref())?; + let reg = args.registry_or_index(config)?; + assert!( + !matches!(reg, Some(RegistryOrIndex::Index(..))), + "must not be index URL" + ); + + ops::registry_logout(config, reg)?; Ok(()) } diff --git a/src/bin/cargo/commands/new.rs b/src/bin/cargo/commands/new.rs index a5dff272108f..c8d19218f99c 100644 --- a/src/bin/cargo/commands/new.rs +++ b/src/bin/cargo/commands/new.rs @@ -7,7 +7,7 @@ pub fn cli() -> Command { .about("Create a new cargo package at ") .arg(Arg::new("path").action(ArgAction::Set).required(true)) .arg_new_opts() - .arg(opt("registry", "Registry to use").value_name("REGISTRY")) + .arg_registry("Registry to use") .arg_quiet() .after_help(color_print::cstr!( "Run `cargo help new` for more detailed information.\n" diff --git a/src/bin/cargo/commands/owner.rs b/src/bin/cargo/commands/owner.rs index c9deaef5137d..00080b8292b8 100644 --- a/src/bin/cargo/commands/owner.rs +++ b/src/bin/cargo/commands/owner.rs @@ -24,9 +24,9 @@ pub fn cli() -> Command { .short('r'), ) .arg(flag("list", "List owners of a crate").short('l')) - .arg(opt("index", "Registry index to modify owners for").value_name("INDEX")) + .arg_index("Registry index URL to modify owners for") + .arg_registry("Registry to modify owners for") .arg(opt("token", "API token to use when authenticating").value_name("TOKEN")) - .arg(opt("registry", "Registry to use").value_name("REGISTRY")) .arg_quiet() .after_help(color_print::cstr!( "Run `cargo help owner` for more detailed information.\n" @@ -34,11 +34,10 @@ pub fn cli() -> Command { } pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult { - let registry = args.registry(config)?; let opts = OwnersOptions { krate: args.get_one::("crate").cloned(), token: args.get_one::("token").cloned().map(Secret::from), - index: args.get_one::("index").cloned(), + reg_or_index: args.registry_or_index(config)?, to_add: args .get_many::("add") .map(|xs| xs.cloned().collect()), @@ -46,7 +45,6 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult { .get_many::("remove") .map(|xs| xs.cloned().collect()), list: args.flag("list"), - registry, }; ops::modify_owners(config, &opts)?; Ok(()) diff --git a/src/bin/cargo/commands/publish.rs b/src/bin/cargo/commands/publish.rs index 34108d45d257..8ce2ffc5bcd5 100644 --- a/src/bin/cargo/commands/publish.rs +++ b/src/bin/cargo/commands/publish.rs @@ -6,8 +6,8 @@ pub fn cli() -> Command { subcommand("publish") .about("Upload a package to the registry") .arg_dry_run("Perform all checks without uploading") - .arg_index() - .arg(opt("registry", "Registry to publish to").value_name("REGISTRY")) + .arg_index("Registry index URL to upload the package to") + .arg_registry("Registry to upload the package to") .arg(opt("token", "Token to use when uploading").value_name("TOKEN")) .arg(flag( "no-verify", @@ -30,7 +30,7 @@ pub fn cli() -> Command { } pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult { - let registry = args.registry(config)?; + let reg_or_index = args.registry_or_index(config)?; let ws = args.workspace(config)?; if ws.root_maybe().is_embedded() { return Err(anyhow::format_err!( @@ -39,7 +39,6 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult { ) .into()); } - let index = args.index()?; ops::publish( &ws, @@ -48,7 +47,7 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult { token: args .get_one::("token") .map(|s| s.to_string().into()), - index, + reg_or_index, verify: !args.flag("no-verify"), allow_dirty: args.flag("allow-dirty"), to_publish: args.packages_from_flags()?, @@ -56,7 +55,6 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult { jobs: args.jobs()?, keep_going: args.keep_going(), dry_run: args.dry_run(), - registry, cli_features: args.cli_features()?, }, )?; diff --git a/src/bin/cargo/commands/remove.rs b/src/bin/cargo/commands/remove.rs index aebb2f11cab9..c6508a6b2d0e 100644 --- a/src/bin/cargo/commands/remove.rs +++ b/src/bin/cargo/commands/remove.rs @@ -288,7 +288,7 @@ fn spec_has_match( } let version_matches = match (spec.version(), dep.version()) { - (Some(v), Some(vq)) => semver::VersionReq::parse(vq)?.matches(v), + (Some(v), Some(vq)) => semver::VersionReq::parse(vq)?.matches(&v), (Some(_), None) => false, (None, None | Some(_)) => true, }; diff --git a/src/bin/cargo/commands/search.rs b/src/bin/cargo/commands/search.rs index 90aadcb274b5..9cacfc7e8063 100644 --- a/src/bin/cargo/commands/search.rs +++ b/src/bin/cargo/commands/search.rs @@ -15,8 +15,8 @@ pub fn cli() -> Command { ) .value_name("LIMIT"), ) - .arg_index() - .arg(opt("registry", "Registry to use").value_name("REGISTRY")) + .arg_index("Registry index URL to search packages in") + .arg_registry("Registry to search packages in") .arg_quiet() .after_help(color_print::cstr!( "Run `cargo help search` for more detailed information.\n" @@ -24,8 +24,7 @@ pub fn cli() -> Command { } pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult { - let registry = args.registry(config)?; - let index = args.index()?; + let reg_or_index = args.registry_or_index(config)?; let limit = args.value_of_u32("limit")?; let limit = min(100, limit.unwrap_or(10)); let query: Vec<&str> = args @@ -34,6 +33,6 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult { .map(String::as_str) .collect(); let query: String = query.join("+"); - ops::search(&query, config, index, limit, registry)?; + ops::search(&query, config, reg_or_index, limit)?; Ok(()) } diff --git a/src/bin/cargo/commands/yank.rs b/src/bin/cargo/commands/yank.rs index 665683915dda..62d1821c3ad8 100644 --- a/src/bin/cargo/commands/yank.rs +++ b/src/bin/cargo/commands/yank.rs @@ -16,8 +16,8 @@ pub fn cli() -> Command { "undo", "Undo a yank, putting a version back into the index", )) - .arg(opt("index", "Registry index to yank from").value_name("INDEX")) - .arg(opt("registry", "Registry to use").value_name("REGISTRY")) + .arg_index("Registry index URL to yank from") + .arg_registry("Registry to yank from") .arg(opt("token", "API token to use when authenticating").value_name("TOKEN")) .arg_quiet() .after_help(color_print::cstr!( @@ -26,8 +26,6 @@ pub fn cli() -> Command { } pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult { - let registry = args.registry(config)?; - let (krate, version) = resolve_crate( args.get_one::("crate").map(String::as_str), args.get_one::("version").map(String::as_str), @@ -41,9 +39,8 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult { krate.map(|s| s.to_string()), version.map(|s| s.to_string()), args.get_one::("token").cloned().map(Secret::from), - args.get_one::("index").cloned(), + args.registry_or_index(config)?, args.flag("undo"), - registry, )?; Ok(()) } diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index 7a8d007c7e15..d86f3dfc4542 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -728,7 +728,6 @@ unstable_cli_options!( 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"), 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"), - credential_process: bool = ("Add a config setting to fetch registry authentication tokens by calling an external process"), direct_minimal_versions: bool = ("Resolve minimal dependency versions instead of maximum (direct dependencies only)"), doctest_xcompile: bool = ("Compile and run doctests for non-host target using runner config"), dual_proc_macros: bool = ("Build proc-macros for both the host and the target"), @@ -744,7 +743,6 @@ unstable_cli_options!( panic_abort_tests: bool = ("Enable support to run tests with -Cpanic=abort"), profile_rustflags: bool = ("Enable the `rustflags` option in profiles in .cargo/config.toml file"), publish_timeout: bool = ("Enable the `publish.timeout` key in .cargo/config.toml file"), - registry_auth: bool = ("Authentication for alternative registries"), rustdoc_map: bool = ("Allow passing external documentation mappings to rustdoc"), rustdoc_scrape_examples: bool = ("Allows Rustdoc to scrape code examples from reverse-dependencies"), script: bool = ("Enable support for single-file, `.rs` packages"), @@ -818,6 +816,12 @@ const STABILIZED_TERMINAL_WIDTH: &str = const STABILISED_SPARSE_REGISTRY: &str = "The sparse protocol is now the default for crates.io"; +const STABILIZED_CREDENTIAL_PROCESS: &str = + "Authentication with a credential provider is always available."; + +const STABILIZED_REGISTRY_AUTH: &str = + "Authenticated registries are available if a credential provider is configured."; + fn deserialize_build_std<'de, D>(deserializer: D) -> Result>, D::Error> where D: serde::Deserializer<'de>, @@ -1081,6 +1085,8 @@ impl CliUnstable { "sparse-registry" => stabilized_warn(k, "1.68", STABILISED_SPARSE_REGISTRY), "terminal-width" => stabilized_warn(k, "1.68", STABILIZED_TERMINAL_WIDTH), "doctest-in-workspace" => stabilized_warn(k, "1.72", STABILIZED_DOCTEST_IN_WORKSPACE), + "credential-process" => stabilized_warn(k, "1.74", STABILIZED_CREDENTIAL_PROCESS), + "registry-auth" => stabilized_warn(k, "1.74", STABILIZED_REGISTRY_AUTH), // Unstable features // Sorted alphabetically: @@ -1098,7 +1104,6 @@ impl CliUnstable { } "codegen-backend" => self.codegen_backend = parse_empty(k, v)?, "config-include" => self.config_include = parse_empty(k, v)?, - "credential-process" => self.credential_process = parse_empty(k, v)?, "direct-minimal-versions" => self.direct_minimal_versions = parse_empty(k, v)?, "doctest-xcompile" => self.doctest_xcompile = parse_empty(k, v)?, "dual-proc-macros" => self.dual_proc_macros = parse_empty(k, v)?, @@ -1119,7 +1124,6 @@ impl CliUnstable { "panic-abort-tests" => self.panic_abort_tests = parse_empty(k, v)?, "profile-rustflags" => self.profile_rustflags = parse_empty(k, v)?, "publish-timeout" => self.publish_timeout = parse_empty(k, v)?, - "registry-auth" => self.registry_auth = parse_empty(k, v)?, "rustdoc-map" => self.rustdoc_map = parse_empty(k, v)?, "rustdoc-scrape-examples" => self.rustdoc_scrape_examples = parse_empty(k, v)?, "separate-nightlies" => self.separate_nightlies = parse_empty(k, v)?, diff --git a/src/cargo/core/manifest.rs b/src/cargo/core/manifest.rs index a6ccc07ce3a6..7886abec3027 100644 --- a/src/cargo/core/manifest.rs +++ b/src/cargo/core/manifest.rs @@ -19,7 +19,7 @@ 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::{short_hash, Config, Filesystem, PartialVersion}; +use crate::util::{short_hash, Config, Filesystem, RustVersion}; pub enum EitherManifest { Real(Manifest), @@ -58,7 +58,7 @@ pub struct Manifest { original: Rc, unstable_features: Features, edition: Edition, - rust_version: Option, + rust_version: Option, im_a_teapot: Option, default_run: Option, metabuild: Option>, @@ -112,7 +112,7 @@ pub struct ManifestMetadata { pub documentation: Option, // URL pub badges: BTreeMap>, pub links: Option, - pub rust_version: Option, + pub rust_version: Option, } #[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] @@ -401,7 +401,7 @@ impl Manifest { workspace: WorkspaceConfig, unstable_features: Features, edition: Edition, - rust_version: Option, + rust_version: Option, im_a_teapot: Option, default_run: Option, original: Rc, @@ -570,8 +570,8 @@ impl Manifest { self.edition } - pub fn rust_version(&self) -> Option { - self.rust_version + pub fn rust_version(&self) -> Option<&RustVersion> { + self.rust_version.as_ref() } pub fn custom_metadata(&self) -> Option<&toml::Value> { diff --git a/src/cargo/core/package.rs b/src/cargo/core/package.rs index a4a0eaa34706..76f6c405bc3a 100644 --- a/src/cargo/core/package.rs +++ b/src/cargo/core/package.rs @@ -31,7 +31,7 @@ use crate::util::network::http::http_handle_and_timeout; use crate::util::network::http::HttpTimeout; use crate::util::network::retry::{Retry, RetryResult}; use crate::util::network::sleep::SleepTracker; -use crate::util::PartialVersion; +use crate::util::RustVersion; use crate::util::{self, internal, Config, Progress, ProgressStyle}; pub const MANIFEST_PREAMBLE: &str = "\ @@ -104,7 +104,7 @@ pub struct SerializedPackage { #[serde(skip_serializing_if = "Option::is_none")] metabuild: Option>, default_run: Option, - rust_version: Option, + rust_version: Option, } impl Package { @@ -178,7 +178,7 @@ impl Package { self.targets().iter().any(|target| target.proc_macro()) } /// Gets the package's minimum Rust version. - pub fn rust_version(&self) -> Option { + pub fn rust_version(&self) -> Option<&RustVersion> { self.manifest().rust_version() } @@ -263,7 +263,7 @@ impl Package { metabuild: self.manifest().metabuild().cloned(), publish: self.publish().as_ref().cloned(), default_run: self.manifest().default_run().map(|s| s.to_owned()), - rust_version: self.rust_version(), + rust_version: self.rust_version().cloned(), } } } diff --git a/src/cargo/core/package_id_spec.rs b/src/cargo/core/package_id_spec.rs index ec5918b0f851..37a6f5e7b3cd 100644 --- a/src/cargo/core/package_id_spec.rs +++ b/src/cargo/core/package_id_spec.rs @@ -10,7 +10,8 @@ use crate::core::PackageId; use crate::util::edit_distance; use crate::util::errors::CargoResult; use crate::util::interning::InternedString; -use crate::util::{validate_package_name, IntoUrl, ToSemver}; +use crate::util::PartialVersion; +use crate::util::{validate_package_name, IntoUrl}; /// Some or all of the data required to identify a package: /// @@ -24,7 +25,7 @@ use crate::util::{validate_package_name, IntoUrl, ToSemver}; #[derive(Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)] pub struct PackageIdSpec { name: InternedString, - version: Option, + version: Option, url: Option, } @@ -70,7 +71,7 @@ impl PackageIdSpec { let mut parts = spec.splitn(2, [':', '@']); let name = parts.next().unwrap(); let version = match parts.next() { - Some(version) => Some(version.to_semver()?), + Some(version) => Some(version.parse::()?), None => None, }; validate_package_name(name, "pkgid", "")?; @@ -94,12 +95,12 @@ impl PackageIdSpec { spec.query(i) } - /// Convert a `PackageId` to a `PackageIdSpec`, which will have both the `Version` and `Url` + /// Convert a `PackageId` to a `PackageIdSpec`, which will have both the `PartialVersion` and `Url` /// fields filled in. pub fn from_package_id(package_id: PackageId) -> PackageIdSpec { PackageIdSpec { name: package_id.name(), - version: Some(package_id.version().clone()), + version: Some(package_id.version().clone().into()), url: Some(package_id.source_id().url().clone()), } } @@ -125,14 +126,14 @@ impl PackageIdSpec { match frag { Some(fragment) => match fragment.split_once([':', '@']) { Some((name, part)) => { - let version = part.to_semver()?; + let version = part.parse::()?; (InternedString::new(name), Some(version)) } None => { if fragment.chars().next().unwrap().is_alphabetic() { (InternedString::new(&fragment), None) } else { - let version = fragment.to_semver()?; + let version = fragment.parse::()?; (InternedString::new(path_name), Some(version)) } } @@ -151,7 +152,12 @@ impl PackageIdSpec { self.name } - pub fn version(&self) -> Option<&Version> { + /// Full `semver::Version`, if present + pub fn version(&self) -> Option { + self.version.as_ref().and_then(|v| v.version()) + } + + pub fn partial_version(&self) -> Option<&PartialVersion> { self.version.as_ref() } @@ -170,7 +176,8 @@ impl PackageIdSpec { } if let Some(ref v) = self.version { - if v != package_id.version() { + let req = v.exact_req(); + if !req.matches(package_id.version()) { return false; } } @@ -243,8 +250,8 @@ impl PackageIdSpec { your project, and the specification \ `{}` is ambiguous.\n\ Please re-run this command \ - with `-p ` where `` is one \ - of the following:", + with one of the following \ + specifications:", self.name(), self ); @@ -319,7 +326,6 @@ mod tests { use super::PackageIdSpec; use crate::core::{PackageId, SourceId}; use crate::util::interning::InternedString; - use crate::util::ToSemver; use url::Url; #[test] @@ -344,16 +350,25 @@ mod tests { "https://crates.io/foo#1.2.3", PackageIdSpec { name: InternedString::new("foo"), - version: Some("1.2.3".to_semver().unwrap()), + version: Some("1.2.3".parse().unwrap()), url: Some(Url::parse("https://crates.io/foo").unwrap()), }, "https://crates.io/foo#1.2.3", ); + ok( + "https://crates.io/foo#1.2", + PackageIdSpec { + name: InternedString::new("foo"), + version: Some("1.2".parse().unwrap()), + url: Some(Url::parse("https://crates.io/foo").unwrap()), + }, + "https://crates.io/foo#1.2", + ); ok( "https://crates.io/foo#bar:1.2.3", PackageIdSpec { name: InternedString::new("bar"), - version: Some("1.2.3".to_semver().unwrap()), + version: Some("1.2.3".parse().unwrap()), url: Some(Url::parse("https://crates.io/foo").unwrap()), }, "https://crates.io/foo#bar@1.2.3", @@ -362,11 +377,20 @@ mod tests { "https://crates.io/foo#bar@1.2.3", PackageIdSpec { name: InternedString::new("bar"), - version: Some("1.2.3".to_semver().unwrap()), + version: Some("1.2.3".parse().unwrap()), url: Some(Url::parse("https://crates.io/foo").unwrap()), }, "https://crates.io/foo#bar@1.2.3", ); + ok( + "https://crates.io/foo#bar@1.2", + PackageIdSpec { + name: InternedString::new("bar"), + version: Some("1.2".parse().unwrap()), + url: Some(Url::parse("https://crates.io/foo").unwrap()), + }, + "https://crates.io/foo#bar@1.2", + ); ok( "foo", PackageIdSpec { @@ -380,7 +404,7 @@ mod tests { "foo:1.2.3", PackageIdSpec { name: InternedString::new("foo"), - version: Some("1.2.3".to_semver().unwrap()), + version: Some("1.2.3".parse().unwrap()), url: None, }, "foo@1.2.3", @@ -389,21 +413,29 @@ mod tests { "foo@1.2.3", PackageIdSpec { name: InternedString::new("foo"), - version: Some("1.2.3".to_semver().unwrap()), + version: Some("1.2.3".parse().unwrap()), url: None, }, "foo@1.2.3", ); + ok( + "foo@1.2", + PackageIdSpec { + name: InternedString::new("foo"), + version: Some("1.2".parse().unwrap()), + url: None, + }, + "foo@1.2", + ); } #[test] fn bad_parsing() { assert!(PackageIdSpec::parse("baz:").is_err()); assert!(PackageIdSpec::parse("baz:*").is_err()); - assert!(PackageIdSpec::parse("baz:1.0").is_err()); assert!(PackageIdSpec::parse("baz@").is_err()); assert!(PackageIdSpec::parse("baz@*").is_err()); - assert!(PackageIdSpec::parse("baz@1.0").is_err()); + assert!(PackageIdSpec::parse("baz@^1.0").is_err()); assert!(PackageIdSpec::parse("https://baz:1.0").is_err()); assert!(PackageIdSpec::parse("https://#baz:1.0").is_err()); } @@ -421,5 +453,6 @@ mod tests { assert!(!PackageIdSpec::parse("foo:1.2.2").unwrap().matches(foo)); 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)); } } diff --git a/src/cargo/core/resolver/dep_cache.rs b/src/cargo/core/resolver/dep_cache.rs index f4e1cfc126f6..7b6e0661f170 100644 --- a/src/cargo/core/resolver/dep_cache.rs +++ b/src/cargo/core/resolver/dep_cache.rs @@ -20,7 +20,7 @@ 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::PartialVersion; +use crate::util::RustVersion; use anyhow::Context as _; use std::collections::{BTreeSet, HashMap, HashSet}; @@ -36,7 +36,7 @@ pub struct RegistryQueryer<'a> { /// versions first. That allows `cargo update -Z minimal-versions` which will /// specify minimum dependency versions to be used. minimal_versions: bool, - max_rust_version: Option, + max_rust_version: Option, /// a cache of `Candidate`s that fulfil a `Dependency` (and whether `first_minimal_version`) registry_cache: HashMap<(Dependency, bool), Poll>>>, /// a cache of `Dependency`s that are required for a `Summary` @@ -58,14 +58,14 @@ impl<'a> RegistryQueryer<'a> { replacements: &'a [(PackageIdSpec, Dependency)], version_prefs: &'a VersionPreferences, minimal_versions: bool, - max_rust_version: Option, + max_rust_version: Option<&RustVersion>, ) -> Self { RegistryQueryer { registry, replacements, version_prefs, minimal_versions, - max_rust_version, + max_rust_version: max_rust_version.cloned(), registry_cache: HashMap::new(), summary_cache: HashMap::new(), used_replacements: HashMap::new(), @@ -115,7 +115,8 @@ impl<'a> RegistryQueryer<'a> { 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 { + if self.max_rust_version.is_none() || s.rust_version() <= self.max_rust_version.as_ref() + { ret.push(s); } })?; @@ -130,9 +131,8 @@ impl<'a> RegistryQueryer<'a> { .iter() .filter(|(spec, _)| spec.matches(summary.package_id())); - let (spec, dep) = match potential_matches.next() { - None => continue, - Some(replacement) => replacement, + let Some((spec, dep)) = potential_matches.next() else { + continue; }; debug!( "found an override for {} {}", diff --git a/src/cargo/core/resolver/mod.rs b/src/cargo/core/resolver/mod.rs index 9e7d41f60cd4..7d8e8acd4067 100644 --- a/src/cargo/core/resolver/mod.rs +++ b/src/cargo/core/resolver/mod.rs @@ -71,7 +71,7 @@ use crate::util::config::Config; use crate::util::errors::CargoResult; use crate::util::network::PollExt; use crate::util::profile; -use crate::util::PartialVersion; +use crate::util::RustVersion; use self::context::Context; use self::dep_cache::RegistryQueryer; @@ -139,7 +139,7 @@ pub fn resolve( version_prefs: &VersionPreferences, config: Option<&Config>, check_public_visible_dependencies: bool, - mut max_rust_version: Option, + mut max_rust_version: Option<&RustVersion>, ) -> CargoResult { let _p = profile::start("resolving"); let minimal_versions = match config { diff --git a/src/cargo/core/resolver/version_prefs.rs b/src/cargo/core/resolver/version_prefs.rs index a61f7cac37b9..28de77f118a1 100644 --- a/src/cargo/core/resolver/version_prefs.rs +++ b/src/cargo/core/resolver/version_prefs.rs @@ -81,7 +81,7 @@ impl VersionPreferences { mod test { use super::*; use crate::core::SourceId; - use crate::util::PartialVersion; + use crate::util::RustVersion; use std::collections::BTreeMap; fn pkgid(name: &str, version: &str) -> PackageId { @@ -104,7 +104,7 @@ mod test { Vec::new(), &features, None::<&String>, - None::, + None::, ) .unwrap() } diff --git a/src/cargo/core/summary.rs b/src/cargo/core/summary.rs index 02007335830f..128c0db9cb17 100644 --- a/src/cargo/core/summary.rs +++ b/src/cargo/core/summary.rs @@ -1,7 +1,7 @@ use crate::core::{Dependency, PackageId, SourceId}; use crate::util::interning::InternedString; use crate::util::CargoResult; -use crate::util::PartialVersion; +use crate::util::RustVersion; use anyhow::bail; use semver::Version; use std::collections::{BTreeMap, HashMap, HashSet}; @@ -26,7 +26,7 @@ struct Inner { features: Rc, checksum: Option, links: Option, - rust_version: Option, + rust_version: Option, } impl Summary { @@ -35,7 +35,7 @@ impl Summary { dependencies: Vec, features: &BTreeMap>, links: Option>, - rust_version: Option, + rust_version: Option, ) -> CargoResult { // ****CAUTION**** If you change anything here that may raise a new // error, be sure to coordinate that change with either the index @@ -88,8 +88,8 @@ impl Summary { self.inner.links } - pub fn rust_version(&self) -> Option { - self.inner.rust_version + pub fn rust_version(&self) -> Option<&RustVersion> { + self.inner.rust_version.as_ref() } pub fn override_id(mut self, id: PackageId) -> Summary { diff --git a/src/cargo/core/workspace.rs b/src/cargo/core/workspace.rs index 2d41e095625e..db379d780730 100644 --- a/src/cargo/core/workspace.rs +++ b/src/cargo/core/workspace.rs @@ -23,7 +23,7 @@ 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::PartialVersion; +use crate::util::RustVersion; use crate::util::{config::ConfigRelativePath, Config, Filesystem, IntoUrl}; use cargo_util::paths; use cargo_util::paths::normalize_path; @@ -598,7 +598,7 @@ impl<'cfg> Workspace<'cfg> { /// Get the lowest-common denominator `package.rust-version` within the workspace, if specified /// anywhere - pub fn rust_version(&self) -> Option { + pub fn rust_version(&self) -> Option<&RustVersion> { self.members().filter_map(|pkg| pkg.rust_version()).min() } diff --git a/src/cargo/lib.rs b/src/cargo/lib.rs index 913c041b6af6..d0761295fcab 100644 --- a/src/cargo/lib.rs +++ b/src/cargo/lib.rs @@ -78,9 +78,7 @@ //! This is the `#[cargo_test]` proc-macro used by the test suite to define tests. //! - [`credential`](https://github.com/rust-lang/cargo/tree/master/credential) //! This subdirectory contains several packages for implementing the -//! experimental -//! [credential-process](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#credential-process) -//! feature. +//! [credential providers](https://doc.rust-lang.org/nightly/cargo/reference/registry-authentication.html). //! - [`mdman`](https://github.com/rust-lang/cargo/tree/master/crates/mdman) //! ([nightly docs](https://doc.rust-lang.org/nightly/nightly-rustc/mdman/index.html)): //! This is a utility for generating cargo's man pages. See [Building the man diff --git a/src/cargo/ops/cargo_add/mod.rs b/src/cargo/ops/cargo_add/mod.rs index cdfe5881271a..968d6068f6ae 100644 --- a/src/cargo/ops/cargo_add/mod.rs +++ b/src/cargo/ops/cargo_add/mod.rs @@ -32,7 +32,7 @@ use crate::util::toml_mut::dependency::Source; use crate::util::toml_mut::dependency::WorkspaceSource; use crate::util::toml_mut::manifest::DepTable; use crate::util::toml_mut::manifest::LocalManifest; -use crate::util::PartialVersion; +use crate::util::RustVersion; use crate::CargoResult; use crate::Config; use crate_spec::CrateSpec; @@ -564,7 +564,7 @@ fn get_latest_dependency( })?; if config.cli_unstable().msrv_policy && honor_rust_version { - fn parse_msrv(comp: PartialVersion) -> (u64, u64, u64) { + fn parse_msrv(comp: &RustVersion) -> (u64, u64, u64) { (comp.major, comp.minor.unwrap_or(0), comp.patch.unwrap_or(0)) } @@ -624,7 +624,7 @@ fn get_latest_dependency( fn rust_version_incompat_error( dep: &str, - rust_version: PartialVersion, + rust_version: &RustVersion, lowest_rust_version: Option<&Summary>, ) -> anyhow::Error { let mut error_msg = format!( @@ -943,72 +943,69 @@ fn print_dep_table_msg(shell: &mut Shell, dep: &DependencyUI) -> CargoResult<()> if matches!(shell.verbosity(), crate::core::shell::Verbosity::Quiet) { return Ok(()); } + let (activated, deactivated) = dep.features(); if !activated.is_empty() || !deactivated.is_empty() { let prefix = format!("{:>13}", " "); - let suffix = if let Some(version) = &dep.available_version { - let mut version = version.clone(); - version.build = Default::default(); - let version = version.to_string(); - // Avoid displaying the version if it will visually look like the version req that we - // showed earlier - let version_req = dep - .version() - .and_then(|v| semver::VersionReq::parse(v).ok()) - .and_then(|v| precise_version(&v)); - if version_req.as_deref() != Some(version.as_str()) { - format!(" as of v{version}") - } else { - "".to_owned() - } - } else { - "".to_owned() - }; + let suffix = format_features_version_suffix(&dep); - shell.write_stderr(format_args!("{}Features{}:\n", prefix, suffix), &style::NOP)?; + shell.write_stderr(format_args!("{prefix}Features{suffix}:\n"), &style::NOP)?; - const MAX_FEATURE_PRINTS: usize = 50; - - let mut activated_printed = 0; + const MAX_FEATURE_PRINTS: usize = 30; let total_activated = activated.len(); - for feat in activated { - if activated_printed >= MAX_FEATURE_PRINTS { - let remaining = total_activated - activated_printed; - shell.write_stderr( - format_args!("{prefix}... {remaining} more activated features\n"), - &style::NOP, - )?; - break; - } + let total_deactivated = deactivated.len(); - shell.write_stderr(&prefix, &style::NOP)?; - shell.write_stderr('+', &style::GOOD)?; - shell.write_stderr(format_args!(" {}\n", feat), &style::NOP)?; - activated_printed += 1; + 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)?; + } + } else { + shell.write_stderr( + format_args!("{prefix}{total_activated} activated features\n"), + &style::NOP, + )?; } - let mut deactivated_printed = 0; - let total_deactivated = deactivated.len(); - for feat in deactivated { - if activated_printed + deactivated_printed >= MAX_FEATURE_PRINTS { - let remaining = total_deactivated - deactivated_printed; - shell.write_stderr( - format_args!("{prefix}... {remaining} more deactivated features\n"), - &style::NOP, - )?; - break; + 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)?; } - - shell.write_stderr(&prefix, &style::NOP)?; - shell.write_stderr('-', &style::ERROR)?; - shell.write_stderr(format_args!(" {}\n", feat), &style::NOP)?; - deactivated_printed += 1; + } else { + shell.write_stderr( + format_args!("{prefix}{total_deactivated} deactivated features\n"), + &style::NOP, + )?; } } Ok(()) } +fn format_features_version_suffix(dep: &DependencyUI) -> String { + if let Some(version) = &dep.available_version { + let mut version = version.clone(); + version.build = Default::default(); + let version = version.to_string(); + // Avoid displaying the version if it will visually look like the version req that we + // showed earlier + let version_req = dep + .version() + .and_then(|v| semver::VersionReq::parse(v).ok()) + .and_then(|v| precise_version(&v)); + if version_req.as_deref() != Some(version.as_str()) { + format!(" as of v{version}") + } else { + "".to_owned() + } + } else { + "".to_owned() + } +} + // Based on Iterator::is_sorted from nightly std; remove in favor of that when stabilized. fn is_sorted(mut it: impl Iterator) -> bool { let Some(mut last) = it.next() else { diff --git a/src/cargo/ops/cargo_clean.rs b/src/cargo/ops/cargo_clean.rs index d28f759d7076..e05ffecba954 100644 --- a/src/cargo/ops/cargo_clean.rs +++ b/src/cargo/ops/cargo_clean.rs @@ -108,7 +108,7 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> { for spec_str in opts.spec.iter() { // Translate the spec to a Package. let spec = PackageIdSpec::parse(spec_str)?; - if spec.version().is_some() { + if spec.partial_version().is_some() { config.shell().warn(&format!( "version qualifier in `-p {}` is ignored, \ cleaning all versions of `{}` found", diff --git a/src/cargo/ops/cargo_package.rs b/src/cargo/ops/cargo_package.rs index 1a82cbd6b5b3..b6423aa9684f 100644 --- a/src/cargo/ops/cargo_package.rs +++ b/src/cargo/ops/cargo_package.rs @@ -423,7 +423,7 @@ fn build_lock(ws: &Workspace<'_>, orig_pkg: &Package) -> CargoResult { 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(); + let max_rust_version = new_pkg.rust_version().cloned(); // Regenerate Cargo.lock using the old one as a guide. let tmp_ws = Workspace::ephemeral(new_pkg, ws.config(), None, true)?; @@ -437,7 +437,7 @@ fn build_lock(ws: &Workspace<'_>, orig_pkg: &Package) -> CargoResult { None, &[], true, - max_rust_version, + max_rust_version.as_ref(), )?; let pkg_set = ops::get_resolved_packages(&new_resolve, tmp_reg)?; diff --git a/src/cargo/ops/mod.rs b/src/cargo/ops/mod.rs index d4ec442dd918..13613eaf635f 100644 --- a/src/cargo/ops/mod.rs +++ b/src/cargo/ops/mod.rs @@ -30,6 +30,7 @@ pub use self::registry::yank; pub use self::registry::OwnersOptions; pub use self::registry::PublishOpts; pub use self::registry::RegistryCredentialConfig; +pub use self::registry::RegistryOrIndex; pub use self::resolve::{ add_overrides, get_resolved_packages, resolve_with_previous, resolve_ws, resolve_ws_with_opts, WorkspaceResolve, diff --git a/src/cargo/ops/registry/login.rs b/src/cargo/ops/registry/login.rs index e523737340f6..e21dfe023dc1 100644 --- a/src/cargo/ops/registry/login.rs +++ b/src/cargo/ops/registry/login.rs @@ -16,16 +16,23 @@ use cargo_credential::Secret; use super::get_source_id; use super::registry; +use super::RegistryOrIndex; pub fn registry_login( config: &Config, token_from_cmdline: Option>, - reg: Option<&str>, + reg_or_index: Option<&RegistryOrIndex>, args: &[&str], ) -> CargoResult<()> { - let source_ids = get_source_id(config, None, reg)?; - - let login_url = match registry(config, token_from_cmdline.clone(), None, reg, false, None) { + let source_ids = get_source_id(config, reg_or_index)?; + + let login_url = match registry( + config, + token_from_cmdline.clone(), + reg_or_index, + false, + None, + ) { Ok((registry, _)) => Some(format!("{}/me", registry.host())), Err(e) if e.is::() => e .downcast::() diff --git a/src/cargo/ops/registry/logout.rs b/src/cargo/ops/registry/logout.rs index d1f080baefb8..fa0cbc539abe 100644 --- a/src/cargo/ops/registry/logout.rs +++ b/src/cargo/ops/registry/logout.rs @@ -8,9 +8,10 @@ use crate::CargoResult; use crate::Config; use super::get_source_id; +use super::RegistryOrIndex; -pub fn registry_logout(config: &Config, reg: Option<&str>) -> CargoResult<()> { - let source_ids = get_source_id(config, None, reg)?; +pub fn registry_logout(config: &Config, reg_or_index: Option) -> CargoResult<()> { + let source_ids = get_source_id(config, reg_or_index.as_ref())?; auth::logout(config, &source_ids.original)?; Ok(()) } diff --git a/src/cargo/ops/registry/mod.rs b/src/cargo/ops/registry/mod.rs index 7d4e99f6bcb4..c82230a16946 100644 --- a/src/cargo/ops/registry/mod.rs +++ b/src/cargo/ops/registry/mod.rs @@ -16,6 +16,7 @@ use std::task::Poll; use anyhow::{bail, format_err, Context as _}; use cargo_credential::{Operation, Secret}; use crates_io::{self, Registry}; +use url::Url; use crate::core::SourceId; use crate::sources::source::Source; @@ -24,7 +25,6 @@ use crate::util::auth; use crate::util::config::{Config, PathAndArgs}; use crate::util::errors::CargoResult; use crate::util::network::http::http_handle; -use crate::util::IntoUrl; pub use self::login::registry_login; pub use self::logout::registry_logout; @@ -35,6 +35,19 @@ pub use self::publish::PublishOpts; pub use self::search::search; pub use self::yank::yank; +/// Represents either `--registry` or `--index` argument, which is mutually exclusive. +#[derive(Debug, Clone)] +pub enum RegistryOrIndex { + Registry(String), + Index(Url), +} + +impl RegistryOrIndex { + fn is_index(&self) -> bool { + matches!(self, RegistryOrIndex::Index(..)) + } +} + /// Registry settings loaded from config files. /// /// This is loaded based on the `--registry` flag and the config settings. @@ -103,14 +116,14 @@ impl RegistryCredentialConfig { fn registry( config: &Config, token_from_cmdline: Option>, - index: Option<&str>, - registry: Option<&str>, + reg_or_index: Option<&RegistryOrIndex>, force_update: bool, token_required: Option>, ) -> CargoResult<(Registry, RegistrySourceIds)> { - let source_ids = get_source_id(config, index, registry)?; + let source_ids = get_source_id(config, reg_or_index)?; - if token_required.is_some() && index.is_some() && token_from_cmdline.is_none() { + let is_index = reg_or_index.map(|v| v.is_index()).unwrap_or_default(); + if is_index && token_required.is_some() && token_from_cmdline.is_none() { bail!("command-line argument --index requires --token to be specified"); } if let Some(token) = token_from_cmdline { @@ -145,6 +158,7 @@ fn registry( None, operation, vec![], + false, )?) } else { None @@ -171,13 +185,12 @@ fn registry( /// crates.io (such as index.crates.io), while the second is always the original source. fn get_source_id( config: &Config, - index: Option<&str>, - reg: Option<&str>, + reg_or_index: Option<&RegistryOrIndex>, ) -> CargoResult { - let sid = match (reg, index) { - (None, None) => SourceId::crates_io(config)?, - (_, Some(i)) => SourceId::for_registry(&i.into_url()?)?, - (Some(r), None) => SourceId::alt_registry(config, r)?, + let sid = match reg_or_index { + None => SourceId::crates_io(config)?, + Some(RegistryOrIndex::Index(url)) => SourceId::for_registry(url)?, + Some(RegistryOrIndex::Registry(r)) => SourceId::alt_registry(config, r)?, }; // Load source replacements that are built-in to Cargo. let builtin_replacement_sid = SourceConfigMap::empty(config)? @@ -186,7 +199,7 @@ fn get_source_id( let replacement_sid = SourceConfigMap::new(config)? .load(sid, &HashSet::new())? .replaced_source_id(); - if reg.is_none() && index.is_none() && replacement_sid != builtin_replacement_sid { + if reg_or_index.is_none() && replacement_sid != builtin_replacement_sid { // Neither --registry nor --index was passed and the user has configured source-replacement. if let Some(replacement_name) = replacement_sid.alt_registry_key() { bail!("crates-io is replaced with remote registry {replacement_name};\ninclude `--registry {replacement_name}` or `--registry crates-io`"); diff --git a/src/cargo/ops/registry/owner.rs b/src/cargo/ops/registry/owner.rs index e29c6400b6b5..3f49bb5bb0b6 100644 --- a/src/cargo/ops/registry/owner.rs +++ b/src/cargo/ops/registry/owner.rs @@ -13,14 +13,15 @@ use crate::util::important_paths::find_root_manifest_for_wd; use crate::CargoResult; use crate::Config; +use super::RegistryOrIndex; + pub struct OwnersOptions { pub krate: Option, pub token: Option>, - pub index: Option, + pub reg_or_index: Option, pub to_add: Option>, pub to_remove: Option>, pub list: bool, - pub registry: Option, } pub fn modify_owners(config: &Config, opts: &OwnersOptions) -> CargoResult<()> { @@ -38,8 +39,7 @@ pub fn modify_owners(config: &Config, opts: &OwnersOptions) -> CargoResult<()> { let (mut registry, _) = super::registry( config, opts.token.as_ref().map(Secret::as_deref), - opts.index.as_deref(), - opts.registry.as_deref(), + opts.reg_or_index.as_ref(), true, Some(operation), )?; diff --git a/src/cargo/ops/registry/publish.rs b/src/cargo/ops/registry/publish.rs index 6e43ca2dbcc6..a88a0a30f0ac 100644 --- a/src/cargo/ops/registry/publish.rs +++ b/src/cargo/ops/registry/publish.rs @@ -37,11 +37,12 @@ use crate::CargoResult; use crate::Config; use super::super::check_dep_has_version; +use super::RegistryOrIndex; pub struct PublishOpts<'cfg> { pub config: &'cfg Config, pub token: Option>, - pub index: Option, + pub reg_or_index: Option, pub verify: bool, pub allow_dirty: bool, pub jobs: Option, @@ -49,7 +50,6 @@ pub struct PublishOpts<'cfg> { pub to_publish: ops::Packages, pub targets: Vec, pub dry_run: bool, - pub registry: Option, pub cli_features: CliFeatures, } @@ -76,7 +76,10 @@ pub fn publish(ws: &Workspace<'_>, opts: &PublishOpts<'_>) -> CargoResult<()> { let (pkg, cli_features) = pkgs.pop().unwrap(); - let mut publish_registry = opts.registry.clone(); + let mut publish_registry = match opts.reg_or_index.as_ref() { + Some(RegistryOrIndex::Registry(registry)) => Some(registry.clone()), + _ => None, + }; if let Some(ref allowed_registries) = *pkg.publish() { if publish_registry.is_none() && allowed_registries.len() == 1 { // If there is only one allowed registry, push to that one directly, @@ -116,11 +119,16 @@ pub fn publish(ws: &Workspace<'_>, opts: &PublishOpts<'_>) -> CargoResult<()> { let ver = pkg.version().to_string(); let operation = Operation::Read; + let reg_or_index = match opts.reg_or_index.clone() { + Some(RegistryOrIndex::Registry(_)) | None => { + publish_registry.map(RegistryOrIndex::Registry) + } + val => val, + }; let (mut registry, reg_ids) = super::registry( opts.config, opts.token.as_ref().map(Secret::as_deref), - opts.index.as_deref(), - publish_registry.as_deref(), + reg_or_index.as_ref(), true, Some(operation).filter(|_| !opts.dry_run), )?; @@ -161,6 +169,7 @@ pub fn publish(ws: &Workspace<'_>, opts: &PublishOpts<'_>) -> CargoResult<()> { None, operation, vec![], + false, )?)); } diff --git a/src/cargo/ops/registry/search.rs b/src/cargo/ops/registry/search.rs index 6c4a10324dec..10b4d600e6b6 100644 --- a/src/cargo/ops/registry/search.rs +++ b/src/cargo/ops/registry/search.rs @@ -13,15 +13,16 @@ use crate::util::truncate_with_ellipsis; use crate::CargoResult; use crate::Config; +use super::RegistryOrIndex; + pub fn search( query: &str, config: &Config, - index: Option, + reg_or_index: Option, limit: u32, - reg: Option, ) -> CargoResult<()> { let (mut registry, source_ids) = - super::registry(config, None, index.as_deref(), reg.as_deref(), false, None)?; + super::registry(config, None, reg_or_index.as_ref(), false, None)?; let (crates, total_crates) = registry.search(query, limit).with_context(|| { format!( "failed to retrieve search results from the registry at {}", diff --git a/src/cargo/ops/registry/yank.rs b/src/cargo/ops/registry/yank.rs index d286093b6fda..2a258ea81772 100644 --- a/src/cargo/ops/registry/yank.rs +++ b/src/cargo/ops/registry/yank.rs @@ -13,14 +13,15 @@ use crate::util::config::Config; use crate::util::errors::CargoResult; use crate::util::important_paths::find_root_manifest_for_wd; +use super::RegistryOrIndex; + pub fn yank( config: &Config, krate: Option, version: Option, token: Option>, - index: Option, + reg_or_index: Option, undo: bool, - reg: Option, ) -> CargoResult<()> { let name = match krate { Some(name) => name, @@ -49,8 +50,7 @@ pub fn yank( let (mut registry, _) = super::registry( config, token.as_ref().map(Secret::as_deref), - index.as_deref(), - reg.as_deref(), + reg_or_index.as_ref(), true, Some(message), )?; diff --git a/src/cargo/ops/resolve.rs b/src/cargo/ops/resolve.rs index 934eb3571d40..053098d5559d 100644 --- a/src/cargo/ops/resolve.rs +++ b/src/cargo/ops/resolve.rs @@ -69,7 +69,7 @@ use crate::core::{GitReference, PackageId, PackageIdSpec, PackageSet, SourceId, use crate::ops; use crate::sources::PathSource; use crate::util::errors::CargoResult; -use crate::util::PartialVersion; +use crate::util::RustVersion; use crate::util::{profile, CanonicalUrl}; use anyhow::Context as _; use std::collections::{HashMap, HashSet}; @@ -133,7 +133,7 @@ pub fn resolve_ws_with_opts<'cfg>( specs: &[PackageIdSpec], has_dev_units: HasDevUnits, force_all_targets: ForceAllTargets, - max_rust_version: Option, + max_rust_version: Option<&RustVersion>, ) -> CargoResult> { let mut registry = PackageRegistry::new(ws.config())?; let mut add_patches = true; @@ -240,7 +240,7 @@ pub fn resolve_ws_with_opts<'cfg>( fn resolve_with_registry<'cfg>( ws: &Workspace<'cfg>, registry: &mut PackageRegistry<'cfg>, - max_rust_version: Option, + max_rust_version: Option<&RustVersion>, ) -> CargoResult { let prev = ops::load_pkg_lockfile(ws)?; let mut resolve = resolve_with_previous( @@ -285,7 +285,7 @@ pub fn resolve_with_previous<'cfg>( to_avoid: Option<&HashSet>, specs: &[PackageIdSpec], register_patches: bool, - max_rust_version: Option, + max_rust_version: Option<&RustVersion>, ) -> CargoResult { // We only want one Cargo at a time resolving a crate graph since this can // involve a lot of frobbing of the global caches. diff --git a/src/cargo/sources/registry/download.rs b/src/cargo/sources/registry/download.rs index 08940b3a1db5..9bf838625c15 100644 --- a/src/cargo/sources/registry/download.rs +++ b/src/cargo/sources/registry/download.rs @@ -85,6 +85,7 @@ pub(super) fn download( None, Operation::Read, vec![], + true, )?) } else { None diff --git a/src/cargo/sources/registry/http_remote.rs b/src/cargo/sources/registry/http_remote.rs index 1346b6995ab8..9fe76333b19d 100644 --- a/src/cargo/sources/registry/http_remote.rs +++ b/src/cargo/sources/registry/http_remote.rs @@ -547,9 +547,7 @@ impl<'cfg> RegistryData for HttpRegistry<'cfg> { return Poll::Ready(Ok(LoadResponse::NotFound)); } StatusCode::Unauthorized - if !self.auth_required - && path == Path::new(RegistryConfig::NAME) - && self.config.cli_unstable().registry_auth => + if !self.auth_required && path == Path::new(RegistryConfig::NAME) => { debug!(target: "network", "re-attempting request for config.json with authorization included."); self.fresh.remove(path); @@ -612,10 +610,6 @@ impl<'cfg> RegistryData for HttpRegistry<'cfg> { } } - if !self.config.cli_unstable().registry_auth { - self.auth_required = false; - } - // Looks like we're going to have to do a network request. self.start_fetch()?; @@ -654,6 +648,7 @@ impl<'cfg> RegistryData for HttpRegistry<'cfg> { self.login_url.as_ref(), Operation::Read, self.auth_error_headers.clone(), + true, )?; headers.append(&format!("Authorization: {}", authorization))?; trace!(target: "network", "including authorization for {}", full_url); @@ -724,10 +719,7 @@ impl<'cfg> RegistryData for HttpRegistry<'cfg> { } fn config(&mut self) -> Poll>> { - let mut cfg = ready!(self.config()?).clone(); - if !self.config.cli_unstable().registry_auth { - cfg.auth_required = false; - } + let cfg = ready!(self.config()?).clone(); Poll::Ready(Ok(Some(cfg))) } diff --git a/src/cargo/sources/registry/index.rs b/src/cargo/sources/registry/index.rs index 4b6d5048be6c..dc176fd800e6 100644 --- a/src/cargo/sources/registry/index.rs +++ b/src/cargo/sources/registry/index.rs @@ -91,7 +91,7 @@ use crate::core::{PackageId, SourceId, Summary}; use crate::sources::registry::{LoadResponse, RegistryData}; use crate::util::interning::InternedString; use crate::util::IntoUrl; -use crate::util::{internal, CargoResult, Config, Filesystem, OptVersionReq, PartialVersion}; +use crate::util::{internal, CargoResult, Config, Filesystem, OptVersionReq, RustVersion}; use anyhow::bail; use cargo_util::{paths, registry::make_dep_path}; use semver::Version; @@ -358,7 +358,7 @@ pub struct IndexPackage<'a> { /// /// Added in 2023 (see ), /// can be `None` if published before then or if not set in the manifest. - rust_version: Option, + rust_version: Option, /// The schema version for this entry. /// /// If this is None, it defaults to version `1`. Entries with unknown diff --git a/src/cargo/sources/registry/remote.rs b/src/cargo/sources/registry/remote.rs index 78bf9affc94e..d4b5d67103f3 100644 --- a/src/cargo/sources/registry/remote.rs +++ b/src/cargo/sources/registry/remote.rs @@ -306,10 +306,7 @@ impl<'cfg> RegistryData for RemoteRegistry<'cfg> { match ready!(self.load(Path::new(""), Path::new(RegistryConfig::NAME), None)?) { LoadResponse::Data { raw_data, .. } => { trace!("config loaded"); - let mut cfg: RegistryConfig = serde_json::from_slice(&raw_data)?; - if !self.config.cli_unstable().registry_auth { - cfg.auth_required = false; - } + let cfg: RegistryConfig = serde_json::from_slice(&raw_data)?; Poll::Ready(Ok(Some(cfg))) } _ => Poll::Ready(Ok(None)), diff --git a/src/cargo/util/auth/mod.rs b/src/cargo/util/auth/mod.rs index 9f4a3422f952..d60a0624f7fa 100644 --- a/src/cargo/util/auth/mod.rs +++ b/src/cargo/util/auth/mod.rs @@ -77,6 +77,7 @@ impl RegistryConfigExtended { fn credential_provider( config: &Config, sid: &SourceId, + require_cred_provider_config: bool, show_warnings: bool, ) -> CargoResult>> { let warn = |message: String| { @@ -88,7 +89,9 @@ fn credential_provider( }; let cfg = registry_credential_config_raw(config, sid)?; + let mut global_provider_defined = true; let default_providers = || { + global_provider_defined = false; if config.cli_unstable().asymmetric_token { // Enable the PASETO provider vec![ @@ -101,7 +104,7 @@ fn credential_provider( }; let global_providers = config .get::>>>("registry.global-credential-providers")? - .filter(|p| !p.is_empty() && config.cli_unstable().credential_process) + .filter(|p| !p.is_empty()) .map(|p| { p.iter() .rev() @@ -112,14 +115,14 @@ fn credential_provider( .unwrap_or_else(default_providers); tracing::debug!(?global_providers); - let providers = match cfg { + match cfg { // If there's a specific provider configured for this registry, use it. Some(RegistryConfig { credential_provider: Some(provider), token, secret_key, .. - }) if config.cli_unstable().credential_process => { + }) => { let provider = resolve_credential_alias(config, provider); if let Some(token) = token { if provider[0] != "cargo:token" { @@ -139,7 +142,7 @@ fn credential_provider( ))?; } } - vec![provider] + return Ok(vec![provider]); } // Warning for both `token` and `secret-key`, stating which will be ignored @@ -173,7 +176,6 @@ fn credential_provider( // One or both of the below individual warnings will trigger } } - global_providers } // Check if a `token` is configured that will be ignored. @@ -191,7 +193,6 @@ fn credential_provider( token.definition ))?; } - global_providers } // Check if a asymmetric token is configured that will be ignored. @@ -210,13 +211,18 @@ fn credential_provider( token.definition ))?; } - global_providers } // If we couldn't find a registry-specific provider, use the fallback provider list. - None | Some(RegistryConfig { .. }) => global_providers, + None | Some(RegistryConfig { .. }) => {} }; - Ok(providers) + if !global_provider_defined && require_cred_provider_config { + bail!( + "authenticated registries require a credential-provider to be configured\n\ + see https://doc.rust-lang.org/cargo/reference/registry-authentication.html for details" + ); + } + Ok(global_providers) } /// Get the credential configuration for a `SourceId`. @@ -402,7 +408,7 @@ impl AuthorizationError { // Only display the _TOKEN environment variable suggestion if the `cargo:token` credential // provider is available for the source. Otherwise setting the environment variable will // have no effect. - let display_token_env_help = credential_provider(config, &sid, false)? + let display_token_env_help = credential_provider(config, &sid, false, false)? .iter() .any(|p| p.first().map(String::as_str) == Some("cargo:token")); Ok(AuthorizationError { @@ -496,6 +502,7 @@ fn credential_action( action: Action<'_>, headers: Vec, args: &[&str], + require_cred_provider_config: bool, ) -> CargoResult { let name = if sid.is_crates_io() { Some(CRATES_IO_REGISTRY) @@ -507,7 +514,7 @@ fn credential_action( name, headers, }; - let providers = credential_provider(config, sid, true)?; + let providers = credential_provider(config, sid, require_cred_provider_config, true)?; let mut any_not_found = false; for provider in providers { let args: Vec<&str> = provider @@ -570,8 +577,15 @@ pub fn auth_token( login_url: Option<&Url>, operation: Operation<'_>, headers: Vec, + require_cred_provider_config: bool, ) -> CargoResult { - match auth_token_optional(config, sid, operation, headers)? { + match auth_token_optional( + config, + sid, + operation, + headers, + require_cred_provider_config, + )? { Some(token) => Ok(token.expose()), None => Err(AuthorizationError::new( config, @@ -589,6 +603,7 @@ fn auth_token_optional( sid: &SourceId, operation: Operation<'_>, headers: Vec, + require_cred_provider_config: bool, ) -> CargoResult>> { tracing::trace!("token requested for {}", sid.display_registry_name()); let mut cache = config.credential_cache(); @@ -609,7 +624,14 @@ fn auth_token_optional( } } - let credential_response = credential_action(config, sid, Action::Get(operation), headers, &[]); + let credential_response = credential_action( + config, + sid, + Action::Get(operation), + headers, + &[], + require_cred_provider_config, + ); if let Some(e) = credential_response.as_ref().err() { if let Some(e) = e.downcast_ref::() { if matches!(e, cargo_credential::Error::NotFound) { @@ -648,7 +670,7 @@ fn auth_token_optional( /// Log out from the given registry. pub fn logout(config: &Config, sid: &SourceId) -> CargoResult<()> { - let credential_response = credential_action(config, sid, Action::Logout, vec![], &[]); + let credential_response = credential_action(config, sid, Action::Logout, vec![], &[], false); if let Some(e) = credential_response.as_ref().err() { if let Some(e) = e.downcast_ref::() { if matches!(e, cargo_credential::Error::NotFound) { @@ -677,7 +699,8 @@ pub fn login( options: LoginOptions<'_>, args: &[&str], ) -> CargoResult<()> { - let credential_response = credential_action(config, sid, Action::Login(options), vec![], args)?; + let credential_response = + credential_action(config, sid, Action::Login(options), vec![], args, false)?; let CredentialResponse::Login = credential_response else { bail!("credential provider produced unexpected response for `login` request: {credential_response:?}") }; diff --git a/src/cargo/util/command_prelude.rs b/src/cargo/util/command_prelude.rs index f55e44f2c688..c8dc4df87c1b 100644 --- a/src/cargo/util/command_prelude.rs +++ b/src/cargo/util/command_prelude.rs @@ -1,6 +1,7 @@ use crate::core::compiler::{BuildConfig, MessageFormat, TimingOutput}; use crate::core::resolver::CliFeatures; use crate::core::{Edition, Workspace}; +use crate::ops::registry::RegistryOrIndex; use crate::ops::{CompileFilter, CompileOptions, NewOptions, Packages, VersionControl}; use crate::util::important_paths::find_root_manifest_for_wd; use crate::util::interning::InternedString; @@ -27,6 +28,7 @@ pub use clap::{value_parser, Arg, ArgAction, ArgMatches}; pub use clap::Command; use super::config::JobsConfig; +use super::IntoUrl; pub mod heading { pub const PACKAGE_SELECTION: &str = "Package Selection"; @@ -286,12 +288,21 @@ pub trait CommandExt: Sized { ) } - fn arg_index(self) -> Self { - self._arg(opt("index", "Registry index URL to upload the package to").value_name("INDEX")) + fn arg_registry(self, help: &'static str) -> Self { + self._arg(opt("registry", help).value_name("REGISTRY")) + } + + fn arg_index(self, help: &'static str) -> Self { + // Always conflicts with `--registry`. + self._arg( + opt("index", help) + .value_name("INDEX") + .conflicts_with("registry"), + ) } fn arg_dry_run(self, dry_run: &'static str) -> Self { - self._arg(flag("dry-run", dry_run)) + self._arg(flag("dry-run", dry_run).short('n')) } fn arg_ignore_rust_version(self) -> Self { @@ -735,29 +746,32 @@ Run `{cmd}` to see possible targets." ) } - fn registry(&self, config: &Config) -> CargoResult> { + fn registry_or_index(&self, config: &Config) -> CargoResult> { let registry = self._value_of("registry"); let index = self._value_of("index"); let result = match (registry, index) { - (None, None) => config.default_registry()?, - (None, Some(_)) => { - // If --index is set, then do not look at registry.default. - None - } + (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", "")?; - Some(r.to_string()) + Some(RegistryOrIndex::Registry(r.to_string())) } (Some(_), Some(_)) => { - bail!("both `--index` and `--registry` should not be set at the same time") + // Should be guarded by clap + unreachable!("both `--index` and `--registry` should not be set at the same time") } }; Ok(result) } - fn index(&self) -> CargoResult> { - let index = self._value_of("index").map(|s| s.to_string()); - Ok(index) + fn registry(&self, config: &Config) -> CargoResult> { + match self._value_of("registry").map(|s| s.to_string()) { + None => config.default_registry(), + Some(registry) => { + validate_package_name(®istry, "registry name", "")?; + Ok(Some(registry)) + } + } } fn check_optional_opts( diff --git a/src/cargo/util/graph.rs b/src/cargo/util/graph.rs index ff4018201fe0..abd0e364ad64 100644 --- a/src/cargo/util/graph.rs +++ b/src/cargo/util/graph.rs @@ -1,5 +1,5 @@ use std::borrow::Borrow; -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet, VecDeque}; use std::fmt; pub struct Graph { @@ -87,57 +87,107 @@ impl Graph { false } - /// Resolves one of the paths from the given dependent package down to - /// a leaf. + /// Resolves one of the paths from the given dependent package down to a leaf. + /// + /// The path return will be the shortest path, or more accurately one of the paths with the shortest length. /// /// Each element contains a node along with an edge except the first one. /// The representation would look like: /// /// (Node0,) -> (Node1, Edge01) -> (Node2, Edge12)... - pub fn path_to_bottom<'a>(&'a self, mut pkg: &'a N) -> Vec<(&'a N, Option<&'a E>)> { - let mut result = vec![(pkg, None)]; - while let Some(p) = self.nodes.get(pkg).and_then(|p| { - p.iter() - // Note that we can have "cycles" introduced through dev-dependency - // edges, so make sure we don't loop infinitely. - .find(|&(node, _)| result.iter().all(|p| p.0 != node)) - .map(|(node, edge)| (node, Some(edge))) - }) { - result.push(p); - pkg = p.0; - } - result + pub fn path_to_bottom<'a>(&'a self, pkg: &'a N) -> Vec<(&'a N, Option<&'a E>)> { + self.path_to(pkg, |s, p| s.edges(p)) } - /// Resolves one of the paths from the given dependent package up to - /// the root. + /// Resolves one of the paths from the given dependent package up to the root. + /// + /// The path return will be the shortest path, or more accurately one of the paths with the shortest length. /// /// Each element contains a node along with an edge except the first one. /// The representation would look like: /// /// (Node0,) -> (Node1, Edge01) -> (Node2, Edge12)... - pub fn path_to_top<'a>(&'a self, mut pkg: &'a N) -> Vec<(&'a N, Option<&'a E>)> { - // Note that this implementation isn't the most robust per se, we'll - // likely have to tweak this over time. For now though it works for what - // it's used for! - let mut result = vec![(pkg, None)]; - let first_pkg_depending_on = |pkg, res: &[(&N, Option<&E>)]| { - self.nodes + pub fn path_to_top<'a>(&'a self, pkg: &'a N) -> Vec<(&'a N, Option<&'a E>)> { + self.path_to(pkg, |s, pk| { + // Note that this implementation isn't the most robust per se, we'll + // likely have to tweak this over time. For now though it works for what + // it's used for! + s.nodes .iter() - .filter(|(_, adjacent)| adjacent.contains_key(pkg)) - // Note that we can have "cycles" introduced through dev-dependency - // edges, so make sure we don't loop infinitely. - .find(|&(node, _)| !res.iter().any(|p| p.0 == node)) - .map(|(p, adjacent)| (p, adjacent.get(pkg))) - }; - while let Some(p) = first_pkg_depending_on(pkg, &result) { - result.push(p); - pkg = p.0; + .filter_map(|(p, adjacent)| adjacent.get(pk).map(|e| (p, e))) + }) + } +} + +impl<'s, N: Eq + Ord + Clone + 's, E: Default + Clone + 's> Graph { + fn path_to<'a, F, I>(&'s self, pkg: &'a N, fn_edge: F) -> Vec<(&'a N, Option<&'a E>)> + where + I: Iterator, + F: Fn(&'s Self, &'a N) -> I, + 'a: 's, + { + let mut back_link = BTreeMap::new(); + let mut queue = VecDeque::from([pkg]); + let mut bottom = None; + + while let Some(p) = queue.pop_front() { + bottom = Some(p); + for (child, edge) in fn_edge(&self, p) { + bottom = None; + back_link.entry(child).or_insert_with(|| { + queue.push_back(child); + (p, edge) + }); + } + if bottom.is_some() { + break; + } + } + + let mut result = Vec::new(); + let mut next = + bottom.expect("the only path was a cycle, no dependency graph has this shape"); + while let Some((p, e)) = back_link.remove(&next) { + result.push((next, Some(e))); + next = p; + } + result.push((next, None)); + result.reverse(); + #[cfg(debug_assertions)] + { + for x in result.windows(2) { + let [(n2, _), (n1, Some(e12))] = x else { + unreachable!() + }; + assert!(std::ptr::eq( + self.edge(n1, n2).or(self.edge(n2, n1)).unwrap(), + *e12 + )); + } + let last = result.last().unwrap().0; + // fixme: this may sometimes be wrong when there are cycles. + if !fn_edge(&self, last).next().is_none() { + self.print_for_test(); + unreachable!("The last element in the path should not have outgoing edges"); + } } result } } +#[test] +fn path_to_case() { + let mut new = Graph::new(); + new.link(0, 3); + new.link(1, 0); + new.link(2, 0); + new.link(2, 1); + assert_eq!( + new.path_to_bottom(&2), + vec![(&2, None), (&0, Some(&())), (&3, Some(&()))] + ); +} + impl Default for Graph { fn default() -> Graph { Graph::new() @@ -162,6 +212,35 @@ impl fmt::Debug for Graph { } } +impl Graph { + /// Prints the graph for constructing unit tests. + /// + /// For purposes of graph traversal algorithms the edge values do not matter, + /// and the only value of the node we care about is the order it gets compared in. + /// This constructs a graph with the same topology but with integer keys and unit edges. + #[cfg(debug_assertions)] + fn print_for_test(&self) { + // Isolate and print a test case. + let names = self + .nodes + .keys() + .chain(self.nodes.values().flat_map(|vs| vs.keys())) + .collect::>() + .into_iter() + .collect::>(); + let mut new = Graph::new(); + for n1 in self.nodes.keys() { + let name1 = names.binary_search(&n1).unwrap(); + new.add(name1); + for n2 in self.nodes[n1].keys() { + let name2 = names.binary_search(&n2).unwrap(); + *new.link(name1, name2) = (); + } + } + dbg!(new); + } +} + impl PartialEq for Graph { fn eq(&self, other: &Graph) -> bool { self.nodes.eq(&other.nodes) diff --git a/src/cargo/util/mod.rs b/src/cargo/util/mod.rs index d74351cd0168..5c0393f9f1d7 100644 --- a/src/cargo/util/mod.rs +++ b/src/cargo/util/mod.rs @@ -22,7 +22,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, VersionExt, VersionReqExt}; +pub use self::semver_ext::{OptVersionReq, PartialVersion, RustVersion, VersionExt, VersionReqExt}; pub use self::to_semver::ToSemver; pub use self::vcs::{existing_vcs_repo, FossilRepo, GitRepo, HgRepo, PijulRepo}; pub use self::workspace::{ diff --git a/src/cargo/util/semver_ext.rs b/src/cargo/util/semver_ext.rs index cddaa97c9cd8..1ed12fbf519f 100644 --- a/src/cargo/util/semver_ext.rs +++ b/src/cargo/util/semver_ext.rs @@ -109,14 +109,71 @@ impl From for OptVersionReq { } } -#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone, Debug)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Debug, serde::Serialize)] +#[serde(transparent)] +pub struct RustVersion(PartialVersion); + +impl std::ops::Deref for RustVersion { + type Target = PartialVersion; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::str::FromStr for RustVersion { + type Err = anyhow::Error; + + fn from_str(value: &str) -> Result { + let partial = value.parse::()?; + if partial.pre.is_some() { + anyhow::bail!("unexpected prerelease field, expected a version like \"1.32\"") + } + if partial.build.is_some() { + anyhow::bail!("unexpected prerelease field, expected a version like \"1.32\"") + } + Ok(Self(partial)) + } +} + +impl<'de> serde::Deserialize<'de> for RustVersion { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + UntaggedEnumVisitor::new() + .expecting("SemVer version") + .string(|value| value.parse().map_err(serde::de::Error::custom)) + .deserialize(deserializer) + } +} + +impl Display for RustVersion { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Debug)] pub struct PartialVersion { pub major: u64, pub minor: Option, pub patch: Option, + pub pre: Option, + pub build: Option, } impl PartialVersion { + pub fn version(&self) -> Option { + 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 { @@ -124,50 +181,86 @@ impl PartialVersion { major: self.major, minor: self.minor, patch: self.patch, - pre: Default::default(), + pre: self.pre.as_ref().cloned().unwrap_or_default(), + }], + } + } + + pub fn exact_req(&self) -> VersionReq { + VersionReq { + comparators: vec![Comparator { + op: semver::Op::Exact, + major: self.major, + minor: self.minor, + patch: self.patch, + pre: self.pre.as_ref().cloned().unwrap_or_default(), }], } } } +impl From 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 { - // HACK: `PartialVersion` is a subset of the `VersionReq` syntax that only ever - // has one comparator with a required minor and optional patch, and uses no - // other features. if is_req(value) { anyhow::bail!("unexpected version requirement, expected a version like \"1.32\"") } - let version_req = match semver::VersionReq::parse(value) { - // Exclude semver operators like `^` and pre-release identifiers - Ok(req) if value.chars().all(|c| c.is_ascii_digit() || c == '.') => req, - _ if value.contains('+') => { - anyhow::bail!("unexpected build field, expected a version like \"1.32\"") - } - _ if value.contains('-') => { - anyhow::bail!("unexpected prerelease field, 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, "guarenteed by is_req"); + let comp = version_req.comparators.pop().unwrap(); + assert_eq!(comp.op, semver::Op::Caret, "guarenteed 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, + }) } - _ => anyhow::bail!("expected a version like \"1.32\""), - }; - assert_eq!( - version_req.comparators.len(), - 1, - "guarenteed by character check" - ); - let comp = &version_req.comparators[0]; - assert_eq!(comp.op, semver::Op::Caret, "guarenteed by character check"); - assert_eq!( - comp.pre, - semver::Prerelease::EMPTY, - "guarenteed by character check" - ); - Ok(PartialVersion { - major: comp.major, - minor: comp.minor, - patch: comp.patch, - }) + } } } @@ -181,6 +274,12 @@ impl Display for PartialVersion { 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(()) } } diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index 34161542fb33..787286eee28e 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -30,7 +30,7 @@ use crate::util::errors::{CargoResult, ManifestError}; use crate::util::interning::InternedString; use crate::util::{ self, config::ConfigRelativePath, validate_package_name, Config, IntoUrl, OptVersionReq, - PartialVersion, + RustVersion, }; pub mod embedded; @@ -1182,8 +1182,8 @@ impl<'de> de::Deserialize<'de> for MaybeWorkspaceString { } } -type MaybeWorkspacePartialVersion = MaybeWorkspace; -impl<'de> de::Deserialize<'de> for MaybeWorkspacePartialVersion { +type MaybeWorkspaceRustVersion = MaybeWorkspace; +impl<'de> de::Deserialize<'de> for MaybeWorkspaceRustVersion { fn deserialize(d: D) -> Result where D: de::Deserializer<'de>, @@ -1191,7 +1191,7 @@ impl<'de> de::Deserialize<'de> for MaybeWorkspacePartialVersion { struct Visitor; impl<'de> de::Visitor<'de> for Visitor { - type Value = MaybeWorkspacePartialVersion; + type Value = MaybeWorkspaceRustVersion; fn expecting(&self, f: &mut fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { f.write_str("a semver or workspace") @@ -1201,8 +1201,8 @@ impl<'de> de::Deserialize<'de> for MaybeWorkspacePartialVersion { where E: de::Error, { - let value = value.parse::().map_err(|e| E::custom(e))?; - Ok(MaybeWorkspacePartialVersion::Defined(value)) + let value = value.parse::().map_err(|e| E::custom(e))?; + Ok(MaybeWorkspaceRustVersion::Defined(value)) } fn visit_map(self, map: V) -> Result @@ -1400,16 +1400,14 @@ impl WorkspaceInherit for TomlWorkspaceField { #[serde(rename_all = "kebab-case")] pub struct TomlPackage { edition: Option, - rust_version: Option, + rust_version: Option, name: InternedString, #[serde(deserialize_with = "version_trim_whitespace")] version: MaybeWorkspaceSemverVersion, authors: Option, build: Option, metabuild: Option, - #[serde(rename = "default-target")] default_target: Option, - #[serde(rename = "forced-target")] forced_target: Option, links: Option, exclude: Option, @@ -1490,7 +1488,7 @@ pub struct InheritableFields { exclude: Option>, include: Option>, #[serde(rename = "rust-version")] - rust_version: Option, + rust_version: Option, // We use skip here since it will never be present when deserializing // and we don't want it present when serializing #[serde(skip)] @@ -1530,7 +1528,7 @@ impl InheritableFields { ("package.license", license -> String), ("package.publish", publish -> VecStringOrBool), ("package.repository", repository -> String), - ("package.rust-version", rust_version -> PartialVersion), + ("package.rust-version", rust_version -> RustVersion), ("package.version", version -> semver::Version), } @@ -1961,8 +1959,9 @@ impl TomlManifest { } let rust_version = if let Some(rust_version) = &package.rust_version { - let rust_version = - rust_version.resolve("rust_version", || inherit()?.rust_version())?; + let rust_version = rust_version + .clone() + .resolve("rust_version", || inherit()?.rust_version())?; let req = rust_version.caret_req(); if let Some(first_version) = edition.first_version() { let unsupported = @@ -2244,7 +2243,7 @@ impl TomlManifest { deps, me.features.as_ref().unwrap_or(&empty_features), package.links.as_deref(), - rust_version, + rust_version.clone(), )?; let metadata = ManifestMetadata { @@ -2357,7 +2356,7 @@ impl TomlManifest { .categories .as_ref() .map(|_| MaybeWorkspace::Defined(metadata.categories.clone())); - package.rust_version = rust_version.map(|rv| MaybeWorkspace::Defined(rv)); + package.rust_version = rust_version.clone().map(|rv| MaybeWorkspace::Defined(rv)); package.exclude = package .exclude .as_ref() @@ -2651,8 +2650,8 @@ 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)) + .lock_version(&version); replace.push((spec, dep)); } Ok(replace) diff --git a/src/doc/man/cargo-login.md b/src/doc/man/cargo-login.md index 54c823d2d8d3..197636da8dfc 100644 --- a/src/doc/man/cargo-login.md +++ b/src/doc/man/cargo-login.md @@ -2,18 +2,25 @@ ## NAME -cargo-login --- Save an API token from the registry locally +cargo-login --- Log in to a registry ## SYNOPSIS -`cargo login` [_options_] [_token_] +`cargo login` [_options_] [_token_] -- [_args_] ## DESCRIPTION -This command will save the API token to disk so that commands that require -authentication, such as {{man "cargo-publish" 1}}, will be automatically -authenticated. The token is saved in `$CARGO_HOME/credentials.toml`. `CARGO_HOME` -defaults to `.cargo` in your home directory. +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. + +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. + +If a registry has a credential-provider specified, it will be used. Otherwise, +the providers from the config value `registry.global-credential-providers` will +be attempted, starting from the end of the list. If the _token_ argument is not specified, it will be read from stdin. @@ -43,9 +50,13 @@ Take care to keep the token secret, it should not be shared with anyone else. ## EXAMPLES -1. Save the API token to disk: +1. Save the token for the default registry: cargo login +2. Save the token for a specific registry: + + cargo login --registry my-registry + ## SEE ALSO {{man "cargo" 1}}, {{man "cargo-logout" 1}}, {{man "cargo-publish" 1}} diff --git a/src/doc/man/cargo-logout.md b/src/doc/man/cargo-logout.md index f9c0db58cd63..d8277bf54dcf 100644 --- a/src/doc/man/cargo-logout.md +++ b/src/doc/man/cargo-logout.md @@ -10,9 +10,15 @@ cargo-logout --- Remove an API token from the registry locally ## DESCRIPTION -This command will remove the API token from the local credential storage. -Credentials are stored in `$CARGO_HOME/credentials.toml` where `$CARGO_HOME` -defaults to `.cargo` in your home directory. +This command will run a credential provider to remove a saved token. + +For the default `cargo:token` credential provider, credentials are stored +in `$CARGO_HOME/credentials.toml` where `$CARGO_HOME` defaults to `.cargo` +in your home directory. + +If a registry has a credential-provider specified, it will be used. Otherwise, +the providers from the config value `registry.global-credential-providers` will +be attempted, starting from the end of the list. If `--registry` is not specified, then the credentials for the default registry will be removed (configured by diff --git a/src/doc/man/generated_txt/cargo-login.txt b/src/doc/man/generated_txt/cargo-login.txt index cce8efcfbddc..ae8127fc9d99 100644 --- a/src/doc/man/generated_txt/cargo-login.txt +++ b/src/doc/man/generated_txt/cargo-login.txt @@ -1,16 +1,24 @@ CARGO-LOGIN(1) NAME - cargo-login — Save an API token from the registry locally + cargo-login — Log in to a registry SYNOPSIS - cargo login [options] [token] + cargo login [options] [token] – [args] DESCRIPTION - This command will save the API token to disk so that commands that - require authentication, such as cargo-publish(1), will be automatically - authenticated. The token is saved in $CARGO_HOME/credentials.toml. - CARGO_HOME defaults to .cargo in your home directory. + 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. + + 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. + + If a registry has a credential-provider specified, it will be used. + Otherwise, the providers from the config value + registry.global-credential-providers will be attempted, starting from + the end of the list. If the token argument is not specified, it will be read from stdin. @@ -102,10 +110,14 @@ EXIT STATUS o 101: Cargo failed to complete. EXAMPLES - 1. Save the API token to disk: + 1. Save the token for the default registry: cargo login + 2. Save the token for a specific registry: + + cargo login --registry my-registry + SEE ALSO cargo(1), cargo-logout(1), cargo-publish(1) diff --git a/src/doc/man/generated_txt/cargo-logout.txt b/src/doc/man/generated_txt/cargo-logout.txt index db21a39b4d61..73e50d9a40f7 100644 --- a/src/doc/man/generated_txt/cargo-logout.txt +++ b/src/doc/man/generated_txt/cargo-logout.txt @@ -7,9 +7,16 @@ SYNOPSIS cargo logout [options] DESCRIPTION - This command will remove the API token from the local credential - storage. Credentials are stored in $CARGO_HOME/credentials.toml where - $CARGO_HOME defaults to .cargo in your home directory. + This command will run a credential provider to remove a saved token. + + For the default cargo:token credential provider, credentials are stored + in $CARGO_HOME/credentials.toml where $CARGO_HOME defaults to .cargo in + your home directory. + + If a registry has a credential-provider specified, it will be used. + Otherwise, the providers from the config value + registry.global-credential-providers will be attempted, starting from + the end of the list. If --registry is not specified, then the credentials for the default registry will be removed (configured by registry.default diff --git a/src/doc/src/SUMMARY.md b/src/doc/src/SUMMARY.md index 273936559854..9ebd7915bb9c 100644 --- a/src/doc/src/SUMMARY.md +++ b/src/doc/src/SUMMARY.md @@ -36,6 +36,8 @@ * [Source Replacement](reference/source-replacement.md) * [External Tools](reference/external-tools.md) * [Registries](reference/registries.md) + * [Registry Authentication](reference/registry-authentication.md) + * [Credential Provider Protocol](reference/credential-provider-protocol.md) * [Running a Registry](reference/running-a-registry.md) * [Registry Index](reference/registry-index.md) * [Registry Web API](reference/registry-web-api.md) diff --git a/src/doc/src/commands/cargo-login.md b/src/doc/src/commands/cargo-login.md index e738dca0626d..e2efcfe4fed9 100644 --- a/src/doc/src/commands/cargo-login.md +++ b/src/doc/src/commands/cargo-login.md @@ -2,18 +2,25 @@ ## NAME -cargo-login --- Save an API token from the registry locally +cargo-login --- Log in to a registry ## SYNOPSIS -`cargo login` [_options_] [_token_] +`cargo login` [_options_] [_token_] -- [_args_] ## DESCRIPTION -This command will save the API token to disk so that commands that require -authentication, such as [cargo-publish(1)](cargo-publish.html), will be automatically -authenticated. The token is saved in `$CARGO_HOME/credentials.toml`. `CARGO_HOME` -defaults to `.cargo` in your home directory. +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. + +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. + +If a registry has a credential-provider specified, it will be used. Otherwise, +the providers from the config value `registry.global-credential-providers` will +be attempted, starting from the end of the list. If the _token_ argument is not specified, it will be read from stdin. @@ -122,9 +129,13 @@ details on environment variables that Cargo reads. ## EXAMPLES -1. Save the API token to disk: +1. Save the token for the default registry: cargo login +2. Save the token for a specific registry: + + cargo login --registry my-registry + ## SEE ALSO [cargo(1)](cargo.html), [cargo-logout(1)](cargo-logout.html), [cargo-publish(1)](cargo-publish.html) diff --git a/src/doc/src/commands/cargo-logout.md b/src/doc/src/commands/cargo-logout.md index 16e393b0219c..3cc50f6e9292 100644 --- a/src/doc/src/commands/cargo-logout.md +++ b/src/doc/src/commands/cargo-logout.md @@ -10,9 +10,15 @@ cargo-logout --- Remove an API token from the registry locally ## DESCRIPTION -This command will remove the API token from the local credential storage. -Credentials are stored in `$CARGO_HOME/credentials.toml` where `$CARGO_HOME` -defaults to `.cargo` in your home directory. +This command will run a credential provider to remove a saved token. + +For the default `cargo:token` credential provider, credentials are stored +in `$CARGO_HOME/credentials.toml` where `$CARGO_HOME` defaults to `.cargo` +in your home directory. + +If a registry has a credential-provider specified, it will be used. Otherwise, +the providers from the config value `registry.global-credential-providers` will +be attempted, starting from the end of the list. If `--registry` is not specified, then the credentials for the default registry will be removed (configured by diff --git a/src/doc/src/reference/config.md b/src/doc/src/reference/config.md index 6d0dba29c918..6a479b81bcde 100644 --- a/src/doc/src/reference/config.md +++ b/src/doc/src/reference/config.md @@ -298,7 +298,9 @@ Cargo will search `PATH` for its executable. Configuration values with sensitive information are stored in the `$CARGO_HOME/credentials.toml` file. This file is automatically created and updated -by [`cargo login`] and [`cargo logout`]. It follows the same format as Cargo config files. +by [`cargo login`] and [`cargo logout`] when using the `cargo:token` credential provider. + +It follows the same format as Cargo config files. ```toml [registry] @@ -537,6 +539,26 @@ directory. This option is deprecated and unused. Cargo always has pipelining enabled. +### `[credential-alias]` +* Type: string or array of strings +* Default: empty +* Environment: `CARGO_CREDENTIAL_ALIAS_` + +The `[credential-alias]` table defines credential provider aliases. +These aliases can be referenced as an element of the `registry.global-credential-providers` +array, or as a credential provider for a specific registry +under `registries..credential-provider`. + +If specified as a string, the value will be split on spaces into path and arguments. + +For example, to define an alias called `my-alias`: + +```toml +[credential-alias] +my-alias = ["/usr/bin/cargo-credential-example", "--argument", "value", "--flag"] +``` +See [Registry Authentication](registry-authentication.md) for more information. + ### `[doc]` The `[doc]` table defines options for the [`cargo doc`] command. @@ -947,6 +969,22 @@ commands like [`cargo publish`] that require authentication. Can be overridden with the `--token` command-line option. +#### `registries..credential-provider` +* Type: string or array of path and arguments +* Default: none +* Environment: `CARGO_REGISTRIES__CREDENTIAL_PROVIDER` + +Specifies the credential provider for the given registry. If not set, the +providers in [`registry.global-credential-providers`](#registryglobal-credential-providers) +will be used. + +If specified as a string, path and arguments will be split on spaces. For +paths or arguments that contain spaces, use an array. + +If the value exists in the [`[credential-alias]`](#credential-alias) table, the alias will be used. + +See [Registry Authentication](registry-authentication.md) for more information. + #### `registries.crates-io.protocol` * Type: string * Default: `sparse` @@ -980,6 +1018,22 @@ by default for registry commands like [`cargo publish`]. Can be overridden with the `--registry` command-line option. +#### `registry.credential-provider` +* Type: string or array of path and arguments +* Default: none +* Environment: `CARGO_REGISTRY_CREDENTIAL_PROVIDER` + +Specifies the credential provider for [crates.io]. If not set, the +providers in [`registry.global-credential-providers`](#registryglobal-credential-providers) +will be used. + +If specified as a string, path and arguments will be split on spaces. For +paths or arguments that contain spaces, use an array. + +If the value exists in the `[credential-alias]` table, the alias will be used. + +See [Registry Authentication](registry-authentication.md) for more information. + #### `registry.token` * Type: string * Default: none @@ -991,6 +1045,21 @@ commands like [`cargo publish`] that require authentication. Can be overridden with the `--token` command-line option. +#### `registry.global-credential-providers` +* Type: array +* Default: `["cargo:token"]` +* Environment: `CARGO_REGISTRY_GLOBAL_CREDENTIAL_PROVIDERS` + +Specifies the list of global credential providers. If credential provider is not set +for a specific registry using `registries..credential-provider`, Cargo will use +the credential providers in this list. Providers toward the end of the list have precedence. + +Path and arguments are split on spaces. If the path or arguments contains spaces, the credential +provider should be defined in the [`[credential-alias]`](#credential-alias) table and +referenced here by its alias. + +See [Registry Authentication](registry-authentication.md) for more information. + ### `[source]` The `[source]` table defines the registry sources available. See [Source diff --git a/src/doc/src/reference/credential-provider-protocol.md b/src/doc/src/reference/credential-provider-protocol.md new file mode 100644 index 000000000000..b577493ee76c --- /dev/null +++ b/src/doc/src/reference/credential-provider-protocol.md @@ -0,0 +1,237 @@ +# Credential Provider Protocol +This document describes information for building a Cargo credential provider. For information on +setting up or using a credential provider, see [Registry Authentication](registry-authentication.md). + +When using an external credential provider, Cargo communicates with the credential +provider using stdin/stdout messages passed as single lines of JSON. + +Cargo will always execute the credential provider with the `--cargo-plugin` argument. +This enables a credential provider executable to have additional functionality beyond +what Cargo needs. Additional arguments are included in the JSON via the `args` field. + +## JSON messages +The JSON messages in this document have newlines added for readability. +Actual messages must not contain newlines. + +### Credential hello +* Sent by: credential provider +* Purpose: used to identify the supported protocols on process startup +```javascript +{ + "v":[1] +} +``` + +Requests sent by Cargo will include a `v` field set to one of the versions listed here. +If Cargo does not support any of the versions offered by the credential provider, it will issue an +error and shut down the credential process. + +### Registry information +* Sent by: Cargo +Not a message by itself. Included in all messages sent by Cargo as the `registry` field. +```javascript +{ + // Index URL of the registry + "index-url":"https://github.com/rust-lang/crates.io-index", + // Name of the registry in configuration (optional) + "name": "crates-io", + // HTTP headers received from attempting to access an authenticated registry (optional) + "headers": ["WWW-Authenticate: cargo"] +} +``` + +### Login request +* Sent by: Cargo +* Purpose: collect and store credentials +```javascript +{ + // Protocol version + "v":1, + // Action to perform: login + "kind":"login", + // Registry information (see Registry information) + "registry":{"index-url":"sparse+https://registry-url/index/", "name": "my-registry"}, + // User-specified token from stdin or command line (optional) + "token": "", + // URL that the user could visit to get a token (optional) + "login-url": "http://registry-url/login", + // Additional command-line args (optional) + "args":[] +} +``` + +If the `token` field is set, than the credential provider should use the token provided. If +the `token` is not set, then the credential provider should prompt the user for a token. + +In addition to the arguments that may be passed to the credential provider in +configuration, `cargo login` also supports passing additional command line args +via `cargo login -- `. These additional arguments will be included +in the `args` field after any args from Cargo configuration. + +### Read request +* Sent by: Cargo +* Purpose: Get the credential for reading crate information +```javascript +{ + // Protocol version + "v":1, + // Request kind: get credentials + "kind":"get", + // Action to perform: read crate information + "operation":"read", + // Registry information (see Registry information) + "registry":{"index-url":"sparse+https://registry-url/index/", "name": "my-registry"}, + // Additional command-line args (optional) + "args":[] +} +``` + +### Publish request +* Sent by: Cargo +* Purpose: Get the credential for publishing a crate +```javascript +{ + // Protocol version + "v":1, + // Request kind: get credentials + "kind":"get", + // Action to perform: publish crate + "operation":"publish", + // Crate name + "name":"sample", + // Crate version + "vers":"0.1.0", + // Crate checksum + "cksum":"...", + // Registry information (see Registry information) + "registry":{"index-url":"sparse+https://registry-url/index/", "name": "my-registry"}, + // Additional command-line args (optional) + "args":[] +} +``` + +### Get success response +* Sent by: credential provider +* Purpose: Gives the credential to Cargo +```javascript +{"Ok":{ + // Response kind: this was a get request + "kind":"get", + // Token to send to the registry + "token":"...", + // Cache control. Can be one of the following: + // * "never": do not cache + // * "session": cache for the current cargo session + // * "expires": cache for the current cargo session until expiration + "cache":"expires", + // Unix timestamp (only for "cache": "expires") + "expiration":1693942857, + // Is the token operation independent? + "operation_independent":true +}} +``` + +The `token` will be sent to the registry as the value of the `Authorization` HTTP header. + +`operation_independent` indicates whether the token can be cached across different +operations (such as publishing or fetching). In general this should be `true` unless +the provider wants to generate tokens that are scoped to specific operations. + +### Login success response +* Sent by: credential provider +* Purpose: Indicates the login was successful +```javascript +{"Ok":{ + // Response kind: this was a login request + "kind":"login" +}} +``` + +### Logout success response +* Sent by: credential provider +* Purpose: Indicates the logout was successful +```javascript +{"Ok":{ + // Response kind: this was a logout request + "kind":"logout" +}} +``` + +### Failure response (URL not supported) +* Sent by: credential provider +* Purpose: Gives error information to Cargo +```javascript +{"Err":{ + "kind":"url-not-supported" +}} +``` +Sent if the credential provider is designed +to only handle specific registry URLs, and the given URL +is not supported. Cargo will attempt another provider if +available. + +### Failure response (not found) +* Sent by: credential provider +* Purpose: Gives error information to Cargo +```javascript +{"Err":{ + // Error: The credential could not be found in the provider. + "kind":"not-found" +}} +``` +Sent if the credential could not be found. This is expected for +`get` requests where the credential is not available, or `logout` +requests where there is nothing found to erase. + +### Failure response (operation not supported) +* Sent by: credential provider +* Purpose: Gives error information to Cargo +```javascript +{"Err":{ + // Error: The credential could not be found in the provider. + "kind":"operation-not-supported" +}} +``` +Sent if the credential provider does not support the requested operation. +If a provider only supports `get` and a `login` is requested, the +provider should respond with this error. + +### Failure response (other) +* Sent by: credential provider +* Purpose: Gives error information to Cargo +```javascript +{"Err":{ + // Error: something else has failed + "kind":"other", + // Error message string to be displayed + "message": "free form string error message", + // Detailed cause chain for the error (optional) + "caused-by": ["cause 1", "cause 2"] +}} +``` + +## Example communication to request a token for reading: +1. Cargo spawns the credential process, capturing stdin and stdout. +2. Credential process sends the Hello message to Cargo + ```javascript + { "v": [1] } + ``` +3. Cargo sends the CredentialRequest message to the credential process (newlines added for readability). + ```javascript + { + "v": 1, + "kind": "get", + "operation": "read", + "registry":{"index-url":"sparse+https://registry-url/index/"} + } + ``` +4. Credential process sends the CredentialResponse to Cargo (newlines added for readability). + ```javascript + { + "token": "...", + "cache": "session", + "operation_independent": true + } + ``` +5. Cargo closes the stdin pipe to the credential provider and it exits. +6. Cargo uses the token for the remainder of the session (until Cargo exits) when interacting with this registry. diff --git a/src/doc/src/reference/environment-variables.md b/src/doc/src/reference/environment-variables.md index 4572fe2ee523..37c788f8dec6 100644 --- a/src/doc/src/reference/environment-variables.md +++ b/src/doc/src/reference/environment-variables.md @@ -124,9 +124,12 @@ In summary, the supported environment variables are: * `CARGO_PROFILE__RPATH` --- The rpath linking option, see [`profile..rpath`]. * `CARGO_PROFILE__SPLIT_DEBUGINFO` --- Controls debug file output behavior, see [`profile..split-debuginfo`]. * `CARGO_PROFILE__STRIP` --- Controls stripping of symbols and/or debuginfos, see [`profile..strip`]. +* `CARGO_REGISTRIES__CREDENTIAL_PROVIDER` --- Credential provider for a registry, see [`registries..credential-provider`]. * `CARGO_REGISTRIES__INDEX` --- URL of a registry index, see [`registries..index`]. * `CARGO_REGISTRIES__TOKEN` --- Authentication token of a registry, see [`registries..token`]. +* `CARGO_REGISTRY_CREDENTIAL_PROVIDER` --- Credential provider for [crates.io], see [`registry.credential-provider`]. * `CARGO_REGISTRY_DEFAULT` --- Default registry for the `--registry` flag, see [`registry.default`]. +* `CARGO_REGISTRY_GLOBAL_CREDENTIAL_PROVIDERS` --- Credential providers for registries that do not have a specific provider defined. See [`registry.global-credential-providers`]. * `CARGO_REGISTRY_TOKEN` --- Authentication token for [crates.io], see [`registry.token`]. * `CARGO_TARGET__LINKER` --- The linker to use, see [`target..linker`]. The triple must be [converted to uppercase and underscores](config.md#environment-variables). * `CARGO_TARGET__RUNNER` --- The executable runner, see [`target..runner`]. @@ -187,9 +190,12 @@ In summary, the supported environment variables are: [`profile..rpath`]: config.md#profilenamerpath [`profile..split-debuginfo`]: config.md#profilenamesplit-debuginfo [`profile..strip`]: config.md#profilenamestrip +[`registries..credential-provider`]: config.md#registriesnamecredential-provider [`registries..index`]: config.md#registriesnameindex [`registries..token`]: config.md#registriesnametoken +[`registry.credential-provider`]: config.md#registrycredential-provider [`registry.default`]: config.md#registrydefault +[`registry.global-credential-providers`]: config.md#registryglobal-credential-providers [`registry.token`]: config.md#registrytoken [`target..linker`]: config.md#targettriplelinker [`target..runner`]: config.md#targettriplerunner diff --git a/src/doc/src/reference/manifest.md b/src/doc/src/reference/manifest.md index 3b6885da6a41..5ecbe5117498 100644 --- a/src/doc/src/reference/manifest.md +++ b/src/doc/src/reference/manifest.md @@ -306,9 +306,9 @@ words that would help someone find this crate. keywords = ["gamedev", "graphics"] ``` -> **Note**: [crates.io] has a maximum of 5 keywords. Each keyword must be -> ASCII text, start with a letter, and only contain letters, numbers, `_` or -> `-`, and have at most 20 characters. +> **Note**: [crates.io] allows a maximum of 5 keywords. Each keyword must be +> ASCII text, have at most 20 characters, start with an alphanumeric character, +> and only contain letters, numbers, `_`, `-` or `+`. ### The `categories` field diff --git a/src/doc/src/reference/pkgid-spec.md b/src/doc/src/reference/pkgid-spec.md index bf2e15c8c1f7..7f20973b5092 100644 --- a/src/doc/src/reference/pkgid-spec.md +++ b/src/doc/src/reference/pkgid-spec.md @@ -24,6 +24,7 @@ The formal grammar for a Package Id Specification is: spec := pkgname | proto "://" hostname-and-path [ "#" ( pkgname | semver ) ] pkgname := name [ ("@" | ":" ) semver ] +semver := digits [ "." digits [ "." digits [ "-" prerelease ] [ "+" build ]]] proto := "http" | "git" | ... ``` @@ -40,6 +41,7 @@ The following are references to the `regex` package on `crates.io`: | Spec | Name | Version | |:------------------------------------------------------------|:-------:|:-------:| | `regex` | `regex` | `*` | +| `regex@1.4` | `regex` | `1.4.*` | | `regex@1.4.3` | `regex` | `1.4.3` | | `https://github.com/rust-lang/crates.io-index#regex` | `regex` | `*` | | `https://github.com/rust-lang/crates.io-index#regex@1.4.3` | `regex` | `1.4.3` | diff --git a/src/doc/src/reference/profiles.md b/src/doc/src/reference/profiles.md index faf3efe187fc..489cbfb6403d 100644 --- a/src/doc/src/reference/profiles.md +++ b/src/doc/src/reference/profiles.md @@ -96,6 +96,10 @@ split-debuginfo` flag] and is platform-specific. Some options are only available on the [nightly channel]. The Cargo default may change in the future once more testing has been performed, and support for DWARF is stabilized. +Be aware that Cargo and rustc have different defaults for this option. This +option exists to allow Cargo to experiment on different combinations of flags +thus providing better debugging and developer experience. + [nightly channel]: ../../book/appendix-07-nightly-rust.html [`-C split-debuginfo` flag]: ../../rustc/codegen-options/index.html#split-debuginfo diff --git a/src/doc/src/reference/registries.md b/src/doc/src/reference/registries.md index 74c81943579a..f0554be980ed 100644 --- a/src/doc/src/reference/registries.md +++ b/src/doc/src/reference/registries.md @@ -11,6 +11,10 @@ support publishing new crates directly from Cargo. If you are implementing a registry server, see [Running a Registry] for more details about the protocol between Cargo and a registry. +If you're using a registry that requires authentication, see [Registry Authentication]. +If you are implementing a credential provider, see [Credential Provider Protocol] +for details. + ## Using an Alternate Registry To use a registry other than [crates.io], the name and index URL of the @@ -117,6 +121,8 @@ controlled via the [`registries.crates-io.protocol`] config key. [Source Replacement]: source-replacement.md [Running a Registry]: running-a-registry.md +[Credential Provider Protocol]: credential-provider-protocol.md +[Registry Authentication]: registry-authentication.md [`cargo publish`]: ../commands/cargo-publish.md [`cargo package`]: ../commands/cargo-package.md [`cargo login`]: ../commands/cargo-login.md diff --git a/src/doc/src/reference/registry-authentication.md b/src/doc/src/reference/registry-authentication.md new file mode 100644 index 000000000000..15a96c6e9f63 --- /dev/null +++ b/src/doc/src/reference/registry-authentication.md @@ -0,0 +1,111 @@ +# Registry Authentication +Cargo authenticates to registries with through credential providers. These +credential providers are external executables or built-in providers that Cargo +uses to store and retreive credentials. + +Using alternative registries with authentication *requires* a credential provider to be configured +to avoid unknowningly storing unecrypted credentials on disk. For historical reasons, public +(non-authenticated) registres do not require credential provider configuration and the `cargo:token` +provider is used if no providers are configured. + +Cargo also includes platform-specific providers that use the operating system to securely store +tokens. The `cargo:token` provider is also included which stores credentials in unencrypted plain +text in the [credentials](config.md#credentials) file. + +## Recommended configuration +It's recommended to configure a global credential provider list in `$CARGO_HOME/config.toml` +which defaults to: +* Windows: `%USERPROFILE%\.cargo\config.toml` +* Unix: `~/.cargo/config.toml` + +This recommended configuration uses the operating system provider, with a fallback to `cargo:token` +to look in Cargo's [credentials](config.md#credentials) file or environment variables. + +Some private registries may also recommend a registry-specific credential-provider. Check your +registry's documentation to see if this is the case. + +### macOS configuration +```toml +# ~/.cargo/config.toml +[registry] +global-credential-providers = ["cargo:token", "cargo:macos-keychain"] +``` + +### Linux (libsecret) configuration +```toml +# ~/.cargo/config.toml +[registry] +global-credential-providers = ["cargo:token", "cargo:libsecret"] +``` + +### Windows configuration +```toml +# %USERPROFILE%\.cargo\config.toml +[registry] +global-credential-providers = ["cargo:token", "cargo:wincred"] +``` + +See [`registry.global-credential-providers`](config.md#registryglobal-credential-providers) +for more details. + +## Built-in providers +Cargo includes several built-in credential providers. The available built-in providers +may change in future Cargo releases (though there are currently no plans to do so). + +### `cargo:token` +Uses Cargo's [credentials](config.md#credentials) file to store tokens unencrypted in plain text. +When retreiving tokens, checks the `CARGO_REGISTRIES__TOKEN` environment variable. +If this credential provider is not listed, then the `*_TOKEN` environment variables will not work. + +### `cargo:wincred` +Uses the Windows Credential Manager to store tokens. + +The credentials are stored as `cargo-registry:` in the Credential Manager +under "Windows Credentials". + +### `cargo:macos-keychain` +Uses the macOS Keychain to store tokens. + +The Keychain Access app can be used to view stored tokens. + +### `cargo:libsecret` +Uses [libsecret](https://wiki.gnome.org/Projects/Libsecret) to store tokens. + +On GNOME, credentials can be viewed using [GNOME Keyring](https://wiki.gnome.org/Projects/GnomeKeyring) +applications. + +### `cargo:token-from-stdout ` +Launch a subprocess that returns a token on stdout. Newlines will be trimmed. +* The process inherits the user's stdin and stderr. +* It should exit 0 on success, and nonzero on error. +* [`cargo login`] and [`cargo logout`] are not supported and return an error if used. + +The following environment variables will be provided to the executed command: + +* `CARGO` --- Path to the `cargo` binary executing the command. +* `CARGO_REGISTRY_INDEX_URL` --- The URL of the registry index. +* `CARGO_REGISTRY_NAME_OPT` --- Optional name of the registry. Should not be used as a lookup key. + +Arguments will be passed on to the subcommand. + +[`cargo login`]: ../commands/cargo-login.md +[`cargo logout`]: ../commands/cargo-logout.md + +## Credential plugins +For credential provider plugins that follow Cargo's [credential provider protocol](credential-provider-protocol.md), +the configuration value should be a string with the path to the executable (or the executable name if on the `PATH`). + +For example, to install [cargo-credential-1password](https://crates.io/crates/cargo-credential-1password) +from crates.io do the following: + +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"] +``` + +The values in `global-credential-providers` are split on spaces to into path and command-line arguments. To +define a global credential provider where the path or arguments contain spaces, use +the [`[credential-alias]` table](config.md#credential-alias). diff --git a/src/doc/src/reference/registry-index.md b/src/doc/src/reference/registry-index.md index 7f331b826c13..67af8d3cd34b 100644 --- a/src/doc/src/reference/registry-index.md +++ b/src/doc/src/reference/registry-index.md @@ -35,6 +35,9 @@ The keys are: - `api`: This is the base URL for the web API. This key is optional, but if it is not specified, commands such as [`cargo publish`] will not work. The web API is described below. +- `auth-required`: indicates whether this is a private registry that requires + all operations to be authenticated including API requests, crate downloads + and sparse index updates. ## Download Endpoint @@ -43,6 +46,8 @@ Cargo supports https, http, and file URLs, HTTP redirects, HTTP1 and HTTP2. The exact specifics of TLS support depend on the platform that Cargo is running on, the version of Cargo, and how it was compiled. +If `auth-required: true` is set in `config.json`, the `Authorization` header +will be included with http(s) download requests. ## Index files The rest of the index repository contains one file for each package, where the @@ -274,6 +279,18 @@ The sparse protocol downloads each index file using an individual HTTP request. this results in a large number of small HTTP requests, performance is significantly improved with a server that supports pipelining and HTTP/2. +#### Sparse authentication +Cargo will attempt to fetch the `config.json` file before +fetching any other files. If the server responds with an HTTP 401, then Cargo will assume +that the registry requires authentication and re-attempt the request for `config.json` +with the authentication token included. + +On authentication failure (or a missing authentication token) the server may include a +`www-authenticate` header with a `Cargo login_url=""` challenge to indicate where the user +can go to get a token. + +Registries that require authentication must set `auth-required: true` in `config.json`. + #### Caching Cargo caches the crate metadata files, and captures the `ETag` or `Last-Modified` HTTP header from the server for each entry. When refreshing crate metadata, Cargo diff --git a/src/doc/src/reference/semver.md b/src/doc/src/reference/semver.md index 69d983078b5f..f09250f1a9b2 100644 --- a/src/doc/src/reference/semver.md +++ b/src/doc/src/reference/semver.md @@ -2135,9 +2135,16 @@ std = [] #### Possibly-breaking: removing an optional dependency {#cargo-remove-opt-dep} -Removing an optional dependency can break a project using your library because +Removing an [optional dependency][opt-dep] can break a project using your library because another project may be enabling that dependency via [Cargo features]. +When there is an optional dependency, cargo implicitly defines a feature of +the same name to provide a mechanism to enable the dependency and to check +when it is enabled. This problem can be avoided by using the `dep:` syntax in +the `[features]` table, which disables this implicit feature. Using `dep:` +makes it possible to hide the existence of optional dependencies under more +semantically-relevant names which can be more safely modified. + ```toml # Breaking change example @@ -2152,7 +2159,33 @@ curl = { version = "0.4.31", optional = true } # ..curl removed ``` +```toml +# MINOR CHANGE +# +# This example shows how to avoid breaking changes with optional dependencies. + +########################################################### +# Before +[dependencies] +curl = { version = "0.4.31", optional = true } + +[features] +networking = ["dep:curl"] + +########################################################### +# After +[dependencies] +# Here, one optional dependency was replaced with another. +hyper = { version = "0.14.27", optional = true } + +[features] +networking = ["dep:hyper"] +``` + Mitigation strategies: +* Use the `dep:` syntax in the `[features]` table to avoid exposing optional + dependencies in the first place. See [optional dependencies][opt-dep] for + more information. * Clearly document your features. If the optional dependency is not included in the documented list of features, then you may decide to consider it safe to change undocumented entries. @@ -2166,6 +2199,8 @@ Mitigation strategies: optional dependencies necessary to implement "networking". Then document the "networking" feature. +[opt-dep]: features.md#optional-dependencies + #### Minor: changing dependency features {#cargo-change-dep-feature} It is usually safe to change the features on a dependency, as long as the diff --git a/src/doc/src/reference/specifying-dependencies.md b/src/doc/src/reference/specifying-dependencies.md index a91f14f25f57..746b5fcb25ac 100644 --- a/src/doc/src/reference/specifying-dependencies.md +++ b/src/doc/src/reference/specifying-dependencies.md @@ -57,8 +57,17 @@ using special operators, though it shouldn't be necessary most of the time. ### Caret requirements -**Caret requirements** are an alternative syntax for the default strategy, -`^1.2.3` is exactly equivalent to `1.2.3`. +**Caret requirements** are the default version requirement strategy. +This version strategy allows [SemVer] compatible updates. +They are specified as version requirements with a leading caret (`^`). + +`^1.2.3` is an example of a caret requirement. + +Leaving off the caret is a simplified equivalent syntax to using caret requirements. +While caret requirements are the default, it is recommended to use the +simplified syntax when possible. + +`log = "^1.2.3"` is exactly equivalent to `log = "1.2.3"`. ### Tilde requirements diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index 08e48177b499..456cb22f4cfc 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -101,9 +101,8 @@ For the latest nightly, see the [nightly version] of this page. * [config-include](#config-include) --- Adds the ability for config files to include other files. * [`cargo config`](#cargo-config) --- Adds a new subcommand for viewing config files. * Registries - * [credential-process](#credential-process) --- Adds support for fetching registry tokens from an external authentication program. * [publish-timeout](#publish-timeout) --- Controls the timeout between uploading the crate and being available in the index - * [registry-auth](#registry-auth) --- Adds support for authenticated registries, and generate registry authentication tokens using asymmetric cryptography. + * [asymmetric-token](#asymmetric-token) --- Adds support for authentication tokens using asymmetric cryptography (`cargo:paseto` provider). * Other * [gitoxide](#gitoxide) --- Use `gitoxide` instead of `git2` for a set of operations. * [script](#script) --- Enable support for single-file `.rs` packages. @@ -933,30 +932,6 @@ It requires the `-Zpublish-timeout` command-line options to be set. timeout = 300 # in seconds ``` -## registry-auth -* Tracking Issue: [10474](https://github.com/rust-lang/cargo/issues/10474) -* RFC: [#3139](https://github.com/rust-lang/rfcs/pull/3139) - -Enables Cargo to include the authorization token for API requests, crate downloads -and sparse index updates by adding a configuration option to config.json -in the registry index. - -To use this feature, the registry server must include `"auth-required": true` in -`config.json`, and you must pass the `-Z registry-auth` flag on the Cargo command line. - -When using the sparse protocol, Cargo will attempt to fetch the `config.json` file before -fetching any other files. If the server responds with an HTTP 401, then Cargo will assume -that the registry requires authentication and re-attempt the request for `config.json` -with the authentication token included. - -On authentication failure (or missing authentication token) the server MAY include a -`WWW-Authenticate` header with a `Cargo login_url` challenge to indicate where the user -can go to get a token. - -``` -WWW-Authenticate: Cargo login_url="https://test-registry-login/me -``` - ## asymmetric-token * Tracking Issue: [10519](https://github.com/rust-lang/cargo/issues/10519) * RFC: [#3231](https://github.com/rust-lang/rfcs/pull/3231) @@ -997,267 +972,6 @@ The "footer" (which is part of the signature) will be a JSON string in UTF-8 and PASETO includes the message that was signed, so the server does not have to reconstruct the exact string from the request in order to check the signature. The server does need to check that the signature is valid for the string in the PASETO and that the contents of that string matches the request. If a claim should be expected for the request but is missing in the PASETO then the request must be rejected. -## credential-process -* Tracking Issue: [#8933](https://github.com/rust-lang/cargo/issues/8933) -* RFC: [#2730](https://github.com/rust-lang/rfcs/pull/2730) - -The `credential-process` feature adds a config setting to fetch registry -authentication tokens by calling an external process. - -To use this feature, you must pass the `-Z credential-process` flag on the -command-line. - -### `credential-process` Configuration - -To configure which process to run to fetch the token, specify the process in -the `registry` table in a [config file] with spaces separating arguments. If the -path to the provider or its arguments contain spaces, then it mused be defined in -the `credential-alias` table and referenced instead. - -```toml -[registry] -global-credential-providers = ["/usr/bin/cargo-creds"] -``` - -The provider at the end of the list will be attempted first. This ensures -that when config files are merged, files closer to the project (and ultimatly -environment variables) have precedence. - -In this example, the `my-provider` provider will be attempted first, and if -it cannot provide credentials, then the `cargo:token` provider will be used. - -```toml -[registry] -global-credential-providers = ['cargo:token', 'my-provider'] -``` - -If you want to use a different provider for a specific registry, it can be -specified in the `registries` table: - -```toml -[registries.my-registry] -credential-provider = "/usr/bin/cargo-creds" -``` - -The credential provider for crates.io can be specified as: - -```toml -[registry] -credential-provider = "/usr/bin/cargo-creds" -``` - -The value can be a string with spaces separating arguments or it can be a TOML -array of strings. - -For commonly-used providers, or providers that need to contain spaces in the arguments -or path, the `credential-alias` table can be used. These aliases can be referenced -in `credential-provider` or `global-credential-providers`. - -```toml -[credential-alias] -my-alias = ["/usr/bin/cargo-creds", "--argument"] - -[registry] -global-credential-providers = ["cargo:token", "my-alias"] -``` - -### Built-in providers - -Cargo now includes several built-in credential providers. These providers are -executed within the Cargo process. They are identified with the `cargo:` prefix. - -* `cargo:token` - Uses Cargo's config and `credentials.toml` to store the token (default). -* `cargo:wincred` - Uses the Windows Credential Manager to store the token. -* `cargo:macos-keychain` - Uses the macOS Keychain to store the token. -* `cargo:libsecret` - Uses [libsecret](https://wiki.gnome.org/Projects/Libsecret) to store tokens on Linux systems. -* `cargo:token-from-stdout ` - Launch a subprocess that returns a token - on stdout. Newlines will be trimmed. The process inherits the user's stdin and stderr. - It should exit 0 on success, and nonzero on error. - - With this form, [`cargo login`] and [`cargo logout`] are not supported and - return an error if used. - - The following environment variables will be provided to the executed command: - - * `CARGO` --- Path to the `cargo` binary executing the command. - * `CARGO_REGISTRY_INDEX_URL` --- The URL of the registry index. - * `CARGO_REGISTRY_NAME_OPT` --- Optional name of the registry. Should not be used as a storage key. Not always available. - -* `cargo:paseto` - implements asymmetric token support (RFC3231) as a credential provider. Requires `-Zasymmetric-token`. - - -`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. - -Install the provider with `cargo install cargo-credential-1password` -In the config, add it to `global-credential-providers`: -```toml -[registry] -global-credential-providers = ["cargo-credential-1password"] -``` - -### JSON Interface -When using an external credential provider, Cargo communicates with the credential -provider using stdin/stdout messages passed as a single line of JSON. - -Cargo will always execute the credential provider with the `--cargo-plugin` argument. -This enables a credential provider executable to have additional functionality beyond -how Cargo uses it. - -The messages here have additional newlines added for readability. -Actual messages must not contain newlines. - -#### Credential hello -* Sent by: credential provider -* Purpose: used to identify the supported protocols on process startup -```javascript -{ - "v":[1] -} -``` - -#### Login request -* Sent by: Cargo -* Purpose: collect and store credentials -```javascript -{ - // Protocol version - "v":1, - // Action to perform: login - "kind":"login", - // Registry information - "registry":{"index-url":"sparse+https://registry-url/index/", "name": "my-registry"}, -} -``` - -#### Read request -* Sent by: Cargo -* Purpose: Get the credential for reading crate information -```javascript -{ - // Protocol version - "v":1, - // Request kind: get credentials - "kind":"get", - // Action to perform: read crate information - "operation":"read", - // Registry information - "registry":{"index-url":"sparse+https://registry-url/index/", "name": "my-registry"}, - // Additional command-line args (optional) - "args":[] -} -``` - -#### Publish request -* Sent by: Cargo -* Purpose: Get the credential for publishing a crate -```javascript -{ - // Protocol version - "v":1, - // Request kind: get credentials - "kind":"get", - // Action to perform: publish crate - "operation":"publish", - // Crate name - "name":"sample", - // Crate version - "vers":"0.1.0", - // Crate checksum - "cksum":"...", - // Registry information - "registry":{"index-url":"sparse+https://registry-url/index/", "name": "my-registry"}, - // Additional command-line args (optional) - "args":[] -} -``` - -#### Success response -* Sent by: credential process -* Purpose: Gives the credential to Cargo -```javascript -{"Ok":{ - // Response kind: this was a get request kind - "kind":"get", - // Token to send to the registry - "token":"...", - // Cache control. Can be one of the following: - // * "never" - // * "session" - // * "expires" - "cache":"expires", - // Unix timestamp (only for "cache": "expires") - "expiration":1693942857, - // Is the token operation independent? - "operation_independent":true -}} -``` - -#### Failure response -* Sent by: credential process -* Purpose: Gives error information to Cargo -```javascript -{"Err":{ - // Error: the credential provider does not support the - // registry - "kind":"url-not-supported", - - // Error: The credential could not be found in the provider. - // using `cargo login --registry ...`. - "kind":"not-found", - - // Error: something else has failed - "kind":"other", - "detail": "free form string error message" -}} -``` - -#### Example communication to request a token for reading: -1. Cargo spawns the credential process, capturing stdin and stdout. -2. Credential process sends the Hello message to Cargo - ```javascript - { "v": [1] } - ``` -3. Cargo sends the CredentialRequest message to the credential process (newlines added for readability). - ```javascript - { - "v": 1, - "kind": "get", - "operation": "read", - "registry":{"index-url":"sparse+https://registry-url/index/", "name":"ado2"}, - "args":[] - } - ``` -4. Credential process sends the CredentialResponse to Cargo (newlines added for readability). - ```javascript - { - "token": "...", - "cache": "session", - "operation_independent": false - } - ``` -5. Credential process exits -6. Cargo uses the token for the remainder of the session (until Cargo exits) when interacting with this registry. - -[`cargo login`]: ../commands/cargo-login.md -[`cargo logout`]: ../commands/cargo-logout.md -[`cargo publish`]: ../commands/cargo-publish.md -[`cargo owner`]: ../commands/cargo-owner.md -[`cargo yank`]: ../commands/cargo-yank.md -[`credentials.toml` file]: config.md#credentials -[crates.io]: https://crates.io/ -[config file]: config.md - ## `cargo config` * Original Issue: [#2362](https://github.com/rust-lang/cargo/issues/2362) @@ -1779,7 +1493,7 @@ See [Registry Protocols](registries.md#registry-protocols) for more information. The [`cargo logout`] command has been stabilized in the 1.70 release. [target triple]: ../appendix/glossary.md#target '"target" (glossary)' - +[`cargo logout`]: ../commands/cargo-logout.md ## `doctest-in-workspace` @@ -1798,3 +1512,15 @@ in `cargo build` as an example for more details. [`[lints]`](manifest.html#the-lints-section) (enabled via `-Zlints`) has been stabilized in the 1.74 release. +## credential-process + +The `-Z credential-process` feature has been stabilized in the 1.74 release. + +See [Registry Authentication](registry-authentication.md) documentation for details. + +## registry-auth + +The `-Z registry-auth` feature has been stabilized in the 1.74 release with the additional +requirement that a credential-provider is configured. + +See [Registry Authentication](registry-authentication.md) documentation for details. diff --git a/src/etc/man/cargo-login.1 b/src/etc/man/cargo-login.1 index 1ae1cc626acf..92b3b3f3c308 100644 --- a/src/etc/man/cargo-login.1 +++ b/src/etc/man/cargo-login.1 @@ -4,14 +4,21 @@ .ad l .ss \n[.ss] 0 .SH "NAME" -cargo\-login \[em] Save an API token from the registry locally +cargo\-login \[em] Log in to a registry .SH "SYNOPSIS" -\fBcargo login\fR [\fIoptions\fR] [\fItoken\fR] +\fBcargo login\fR [\fIoptions\fR] [\fItoken\fR] \[en] [\fIargs\fR] .SH "DESCRIPTION" -This command will save the API token to disk so that commands that require -authentication, such as \fBcargo\-publish\fR(1), will be automatically -authenticated. The token is saved in \fB$CARGO_HOME/credentials.toml\fR\&. \fBCARGO_HOME\fR -defaults to \fB\&.cargo\fR in your home directory. +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 +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. +.sp +If a registry has a credential\-provider specified, it will be used. Otherwise, +the providers from the config value \fBregistry.global\-credential\-providers\fR will +be attempted, starting from the end of the list. .sp If the \fItoken\fR argument is not specified, it will be read from stdin. .sp @@ -123,7 +130,7 @@ details on environment variables that Cargo reads. .SH "EXAMPLES" .sp .RS 4 -\h'-04' 1.\h'+01'Save the API token to disk: +\h'-04' 1.\h'+01'Save the token for the default registry: .sp .RS 4 .nf @@ -131,5 +138,15 @@ cargo login .fi .RE .RE +.sp +.RS 4 +\h'-04' 2.\h'+01'Save the token for a specific registry: +.sp +.RS 4 +.nf +cargo login \-\-registry my\-registry +.fi +.RE +.RE .SH "SEE ALSO" \fBcargo\fR(1), \fBcargo\-logout\fR(1), \fBcargo\-publish\fR(1) diff --git a/src/etc/man/cargo-logout.1 b/src/etc/man/cargo-logout.1 index 7333cc62c0fd..8d73e12a4ef3 100644 --- a/src/etc/man/cargo-logout.1 +++ b/src/etc/man/cargo-logout.1 @@ -8,9 +8,15 @@ cargo\-logout \[em] Remove an API token from the registry locally .SH "SYNOPSIS" \fBcargo logout\fR [\fIoptions\fR] .SH "DESCRIPTION" -This command will remove the API token from the local credential storage. -Credentials are stored in \fB$CARGO_HOME/credentials.toml\fR where \fB$CARGO_HOME\fR -defaults to \fB\&.cargo\fR in your home directory. +This command will run a credential provider to remove a saved token. +.sp +For the default \fBcargo:token\fR credential provider, credentials are stored +in \fB$CARGO_HOME/credentials.toml\fR where \fB$CARGO_HOME\fR defaults to \fB\&.cargo\fR +in your home directory. +.sp +If a registry has a credential\-provider specified, it will be used. Otherwise, +the providers from the config value \fBregistry.global\-credential\-providers\fR will +be attempted, starting from the end of the list. .sp If \fB\-\-registry\fR is not specified, then the credentials for the default registry will be removed (configured by diff --git a/tests/testsuite/alt_registry.rs b/tests/testsuite/alt_registry.rs index 91157cd5327c..d6d7dd531ccd 100644 --- a/tests/testsuite/alt_registry.rs +++ b/tests/testsuite/alt_registry.rs @@ -1389,10 +1389,9 @@ fn both_index_and_registry() { p.cargo(cmd) .arg("--registry=foo") .arg("--index=foo") - .with_status(101) - .with_stderr( - "[ERROR] both `--index` and `--registry` \ - should not be set at the same time", + .with_status(1) + .with_stderr_contains( + "error: the argument '--registry ' cannot be used with '--index '", ) .run(); } diff --git a/tests/testsuite/cargo_add/features_too_many_activated/in/Cargo.toml b/tests/testsuite/cargo_add/features_activated_over_limit/in/Cargo.toml similarity index 100% rename from tests/testsuite/cargo_add/features_too_many_activated/in/Cargo.toml rename to tests/testsuite/cargo_add/features_activated_over_limit/in/Cargo.toml diff --git a/tests/testsuite/cargo_add/features_too_many_activated/in/src/lib.rs b/tests/testsuite/cargo_add/features_activated_over_limit/in/src/lib.rs similarity index 100% rename from tests/testsuite/cargo_add/features_too_many_activated/in/src/lib.rs rename to tests/testsuite/cargo_add/features_activated_over_limit/in/src/lib.rs diff --git a/tests/testsuite/cargo_add/features_too_many_activated/mod.rs b/tests/testsuite/cargo_add/features_activated_over_limit/mod.rs similarity index 100% rename from tests/testsuite/cargo_add/features_too_many_activated/mod.rs rename to tests/testsuite/cargo_add/features_activated_over_limit/mod.rs diff --git a/tests/testsuite/cargo_add/features_too_many_activated/out/Cargo.toml b/tests/testsuite/cargo_add/features_activated_over_limit/out/Cargo.toml similarity index 100% rename from tests/testsuite/cargo_add/features_too_many_activated/out/Cargo.toml rename to tests/testsuite/cargo_add/features_activated_over_limit/out/Cargo.toml diff --git a/tests/testsuite/cargo_add/features_activated_over_limit/stderr.log b/tests/testsuite/cargo_add/features_activated_over_limit/stderr.log new file mode 100644 index 000000000000..9288319aa395 --- /dev/null +++ b/tests/testsuite/cargo_add/features_activated_over_limit/stderr.log @@ -0,0 +1,5 @@ + Updating `dummy-registry` index + Adding your-face v99999.0.0 to dependencies. + Features: + 100 activated features + 100 deactivated features diff --git a/tests/testsuite/cargo_add/features_too_many_activated/stdout.log b/tests/testsuite/cargo_add/features_activated_over_limit/stdout.log similarity index 100% rename from tests/testsuite/cargo_add/features_too_many_activated/stdout.log rename to tests/testsuite/cargo_add/features_activated_over_limit/stdout.log diff --git a/tests/testsuite/cargo_add/features_too_many_few_activated/in/Cargo.toml b/tests/testsuite/cargo_add/features_deactivated_over_limit/in/Cargo.toml similarity index 100% rename from tests/testsuite/cargo_add/features_too_many_few_activated/in/Cargo.toml rename to tests/testsuite/cargo_add/features_deactivated_over_limit/in/Cargo.toml diff --git a/tests/testsuite/cargo_add/features_too_many_few_activated/in/src/lib.rs b/tests/testsuite/cargo_add/features_deactivated_over_limit/in/src/lib.rs similarity index 100% rename from tests/testsuite/cargo_add/features_too_many_few_activated/in/src/lib.rs rename to tests/testsuite/cargo_add/features_deactivated_over_limit/in/src/lib.rs diff --git a/tests/testsuite/cargo_add/features_too_many_few_activated/mod.rs b/tests/testsuite/cargo_add/features_deactivated_over_limit/mod.rs similarity index 96% rename from tests/testsuite/cargo_add/features_too_many_few_activated/mod.rs rename to tests/testsuite/cargo_add/features_deactivated_over_limit/mod.rs index 4b7114c16904..b2ed6f87d860 100644 --- a/tests/testsuite/cargo_add/features_too_many_few_activated/mod.rs +++ b/tests/testsuite/cargo_add/features_deactivated_over_limit/mod.rs @@ -8,7 +8,7 @@ use cargo_test_support::curr_dir; #[cargo_test] fn case() { const MANY_FEATURES_COUNT: usize = 200; - const ACTIVATED_FEATURES_COUNT: usize = 10; + const ACTIVATED_FEATURES_COUNT: usize = 30; cargo_test_support::registry::init(); let mut test_package = diff --git a/tests/testsuite/cargo_add/features_deactivated_over_limit/out/Cargo.toml b/tests/testsuite/cargo_add/features_deactivated_over_limit/out/Cargo.toml new file mode 100644 index 000000000000..b94cde668a65 --- /dev/null +++ b/tests/testsuite/cargo_add/features_deactivated_over_limit/out/Cargo.toml @@ -0,0 +1,8 @@ +[workspace] + +[package] +name = "cargo-list-test-fixture" +version = "0.0.0" + +[dependencies] +your-face = { version = "99999.0.0", features = ["eyes000", "eyes001", "eyes002", "eyes003", "eyes004", "eyes005", "eyes006", "eyes007", "eyes008", "eyes009", "eyes010", "eyes011", "eyes012", "eyes013", "eyes014", "eyes015", "eyes016", "eyes017", "eyes018", "eyes019", "eyes020", "eyes021", "eyes022", "eyes023", "eyes024", "eyes025", "eyes026", "eyes027", "eyes028", "eyes029"] } diff --git a/tests/testsuite/cargo_add/features_too_many_activated/stderr.log b/tests/testsuite/cargo_add/features_deactivated_over_limit/stderr.log similarity index 59% rename from tests/testsuite/cargo_add/features_too_many_activated/stderr.log rename to tests/testsuite/cargo_add/features_deactivated_over_limit/stderr.log index 05e69dbc7bca..7f74e6bf0805 100644 --- a/tests/testsuite/cargo_add/features_too_many_activated/stderr.log +++ b/tests/testsuite/cargo_add/features_deactivated_over_limit/stderr.log @@ -31,25 +31,4 @@ + eyes027 + eyes028 + eyes029 - + eyes030 - + eyes031 - + eyes032 - + eyes033 - + eyes034 - + eyes035 - + eyes036 - + eyes037 - + eyes038 - + eyes039 - + eyes040 - + eyes041 - + eyes042 - + eyes043 - + eyes044 - + eyes045 - + eyes046 - + eyes047 - + eyes048 - + eyes049 - ... 50 more activated features - ... 100 more deactivated features + 170 deactivated features diff --git a/tests/testsuite/cargo_add/features_too_many_few_activated/stdout.log b/tests/testsuite/cargo_add/features_deactivated_over_limit/stdout.log similarity index 100% rename from tests/testsuite/cargo_add/features_too_many_few_activated/stdout.log rename to tests/testsuite/cargo_add/features_deactivated_over_limit/stdout.log diff --git a/tests/testsuite/cargo_add/features_too_many_few_activated/out/Cargo.toml b/tests/testsuite/cargo_add/features_too_many_few_activated/out/Cargo.toml deleted file mode 100644 index 82e0197df5a7..000000000000 --- a/tests/testsuite/cargo_add/features_too_many_few_activated/out/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[workspace] - -[package] -name = "cargo-list-test-fixture" -version = "0.0.0" - -[dependencies] -your-face = { version = "99999.0.0", features = ["eyes000", "eyes001", "eyes002", "eyes003", "eyes004", "eyes005", "eyes006", "eyes007", "eyes008", "eyes009"] } diff --git a/tests/testsuite/cargo_add/features_too_many_few_activated/stderr.log b/tests/testsuite/cargo_add/features_too_many_few_activated/stderr.log deleted file mode 100644 index c76c35caea21..000000000000 --- a/tests/testsuite/cargo_add/features_too_many_few_activated/stderr.log +++ /dev/null @@ -1,54 +0,0 @@ - Updating `dummy-registry` index - Adding your-face v99999.0.0 to dependencies. - Features: - + eyes000 - + eyes001 - + eyes002 - + eyes003 - + eyes004 - + eyes005 - + eyes006 - + eyes007 - + eyes008 - + eyes009 - - eyes010 - - eyes011 - - eyes012 - - eyes013 - - eyes014 - - eyes015 - - eyes016 - - eyes017 - - eyes018 - - eyes019 - - eyes020 - - eyes021 - - eyes022 - - eyes023 - - eyes024 - - eyes025 - - eyes026 - - eyes027 - - eyes028 - - eyes029 - - eyes030 - - eyes031 - - eyes032 - - eyes033 - - eyes034 - - eyes035 - - eyes036 - - eyes037 - - eyes038 - - eyes039 - - eyes040 - - eyes041 - - eyes042 - - eyes043 - - eyes044 - - eyes045 - - eyes046 - - eyes047 - - eyes048 - - eyes049 - ... 150 more deactivated features diff --git a/tests/testsuite/cargo_add/help/stdout.log b/tests/testsuite/cargo_add/help/stdout.log index 0daba1a94978..cf2a913132d1 100644 --- a/tests/testsuite/cargo_add/help/stdout.log +++ b/tests/testsuite/cargo_add/help/stdout.log @@ -42,7 +42,7 @@ Options: --ignore-rust-version Ignore `rust-version` specification in packages (unstable) - --dry-run + -n, --dry-run Don't actually write the manifest -q, --quiet diff --git a/tests/testsuite/cargo_add/mod.rs b/tests/testsuite/cargo_add/mod.rs index 47a595cd9299..8c03b30dce67 100644 --- a/tests/testsuite/cargo_add/mod.rs +++ b/tests/testsuite/cargo_add/mod.rs @@ -18,12 +18,12 @@ mod dev_prefer_existing_version; mod dry_run; mod empty_dep_table; mod features; +mod features_activated_over_limit; +mod features_deactivated_over_limit; mod features_empty; mod features_multiple_occurrences; mod features_preserve; mod features_spaced_values; -mod features_too_many_activated; -mod features_too_many_few_activated; mod features_unknown; mod features_unknown_no_features; mod git; diff --git a/tests/testsuite/cargo_login/help/stdout.log b/tests/testsuite/cargo_login/help/stdout.log index faec55c18e76..fd0f3eb3d8fb 100644 --- a/tests/testsuite/cargo_login/help/stdout.log +++ b/tests/testsuite/cargo_login/help/stdout.log @@ -4,7 +4,7 @@ Usage: cargo[EXE] login [OPTIONS] [token] [-- [args]...] Arguments: [token] - [args]... Arguments for the credential provider (unstable) + [args]... Additional arguments for the credential provider Options: --registry Registry to use diff --git a/tests/testsuite/cargo_owner/help/stdout.log b/tests/testsuite/cargo_owner/help/stdout.log index 3c8495ff02ae..580be3c88f99 100644 --- a/tests/testsuite/cargo_owner/help/stdout.log +++ b/tests/testsuite/cargo_owner/help/stdout.log @@ -9,9 +9,9 @@ Options: -a, --add Name of a user or team to invite as an owner -r, --remove Name of a user or team to remove as an owner -l, --list List owners of a crate - --index Registry index to modify owners for + --index Registry index URL to modify owners for + --registry Registry to modify owners for --token API token to use when authenticating - --registry Registry to use -q, --quiet Do not print cargo log messages -v, --verbose... Use verbose output (-vv very verbose/build.rs output) --color Coloring: auto, always, never diff --git a/tests/testsuite/cargo_publish/help/stdout.log b/tests/testsuite/cargo_publish/help/stdout.log index 313b937f91a8..df2594fb4622 100644 --- a/tests/testsuite/cargo_publish/help/stdout.log +++ b/tests/testsuite/cargo_publish/help/stdout.log @@ -3,9 +3,9 @@ Upload a package to the registry Usage: cargo[EXE] publish [OPTIONS] Options: - --dry-run Perform all checks without uploading + -n, --dry-run Perform all checks without uploading --index Registry index URL to upload the package to - --registry Registry to publish to + --registry Registry to upload the package to --token Token to use when uploading --no-verify Don't verify the contents by building them --allow-dirty Allow dirty working directories to be packaged diff --git a/tests/testsuite/cargo_remove/help/stdout.log b/tests/testsuite/cargo_remove/help/stdout.log index b8d16f0c2548..8937fb9f391e 100644 --- a/tests/testsuite/cargo_remove/help/stdout.log +++ b/tests/testsuite/cargo_remove/help/stdout.log @@ -6,7 +6,7 @@ Arguments: ... Dependencies to be removed Options: - --dry-run Don't actually write the manifest + -n, --dry-run Don't actually write the manifest -q, --quiet Do not print cargo log messages -v, --verbose... Use verbose output (-vv very verbose/build.rs output) --color Coloring: auto, always, never diff --git a/tests/testsuite/cargo_search/help/stdout.log b/tests/testsuite/cargo_search/help/stdout.log index 8572064e357c..07170ad7079e 100644 --- a/tests/testsuite/cargo_search/help/stdout.log +++ b/tests/testsuite/cargo_search/help/stdout.log @@ -7,8 +7,8 @@ Arguments: Options: --limit Limit the number of results (default: 10, max: 100) - --index Registry index URL to upload the package to - --registry Registry to use + --index Registry index URL to search packages in + --registry Registry to search packages in -q, --quiet Do not print cargo log messages -v, --verbose... Use verbose output (-vv very verbose/build.rs output) --color Coloring: auto, always, never diff --git a/tests/testsuite/cargo_update/help/stdout.log b/tests/testsuite/cargo_update/help/stdout.log index 111ee72d4fc4..92caeb65601d 100644 --- a/tests/testsuite/cargo_update/help/stdout.log +++ b/tests/testsuite/cargo_update/help/stdout.log @@ -3,7 +3,7 @@ Update dependencies as recorded in the local lock file Usage: cargo[EXE] update [OPTIONS] [SPEC]... Options: - --dry-run Don't actually write the lockfile + -n, --dry-run Don't actually write the lockfile --recursive Force updating all dependencies of [SPEC]... as well --precise Update [SPEC] to exactly PRECISE -q, --quiet Do not print cargo log messages diff --git a/tests/testsuite/cargo_yank/help/stdout.log b/tests/testsuite/cargo_yank/help/stdout.log index 25b04e6c7d08..c6dbfeb9d47e 100644 --- a/tests/testsuite/cargo_yank/help/stdout.log +++ b/tests/testsuite/cargo_yank/help/stdout.log @@ -8,8 +8,8 @@ Arguments: Options: --version The version to yank or un-yank --undo Undo a yank, putting a version back into the index - --index Registry index to yank from - --registry Registry to use + --index Registry index URL to yank from + --registry Registry to yank from --token API token to use when authenticating -q, --quiet Do not print cargo log messages -v, --verbose... Use verbose output (-vv very verbose/build.rs output) diff --git a/tests/testsuite/clean.rs b/tests/testsuite/clean.rs index 65f6cf476166..26cc11a4a2ad 100644 --- a/tests/testsuite/clean.rs +++ b/tests/testsuite/clean.rs @@ -569,10 +569,118 @@ fn assert_all_clean(build_dir: &Path) { } #[cargo_test] -fn clean_spec_multiple() { +fn clean_spec_version() { // clean -p foo where foo matches multiple versions - Package::new("bar", "1.0.0").publish(); - Package::new("bar", "2.0.0").publish(); + Package::new("bar", "0.1.0").publish(); + Package::new("bar", "0.2.0").publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + bar1 = {version="0.1", package="bar"} + bar2 = {version="0.2", package="bar"} + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("build").run(); + + // Check suggestion for bad pkgid. + p.cargo("clean -p baz") + .with_status(101) + .with_stderr( + "\ +error: package ID specification `baz` did not match any packages + +Did you mean `bar`? +", + ) + .run(); + + p.cargo("clean -p bar:0.1.0") + .with_stderr( + "warning: version qualifier in `-p bar:0.1.0` is ignored, \ + cleaning all versions of `bar` found", + ) + .run(); + let mut walker = walkdir::WalkDir::new(p.build_dir()) + .into_iter() + .filter_map(|e| e.ok()) + .filter(|e| { + let n = e.file_name().to_str().unwrap(); + n.starts_with("bar") || n.starts_with("libbar") + }); + if let Some(e) = walker.next() { + panic!("{:?} was not cleaned", e.path()); + } +} + +#[cargo_test] +fn clean_spec_partial_version() { + // clean -p foo where foo matches multiple versions + Package::new("bar", "0.1.0").publish(); + Package::new("bar", "0.2.0").publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + bar1 = {version="0.1", package="bar"} + bar2 = {version="0.2", package="bar"} + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("build").run(); + + // Check suggestion for bad pkgid. + p.cargo("clean -p baz") + .with_status(101) + .with_stderr( + "\ +error: package ID specification `baz` did not match any packages + +Did you mean `bar`? +", + ) + .run(); + + p.cargo("clean -p bar:0.1") + .with_stderr( + "warning: version qualifier in `-p bar:0.1` is ignored, \ + cleaning all versions of `bar` found", + ) + .run(); + let mut walker = walkdir::WalkDir::new(p.build_dir()) + .into_iter() + .filter_map(|e| e.ok()) + .filter(|e| { + let n = e.file_name().to_str().unwrap(); + n.starts_with("bar") || n.starts_with("libbar") + }); + if let Some(e) = walker.next() { + panic!("{:?} was not cleaned", e.path()); + } +} + +#[cargo_test] +fn clean_spec_partial_version_ambiguous() { + // clean -p foo where foo matches multiple versions + Package::new("bar", "0.1.0").publish(); + Package::new("bar", "0.2.0").publish(); let p = project() .file( @@ -583,8 +691,8 @@ fn clean_spec_multiple() { version = "0.1.0" [dependencies] - bar1 = {version="1.0", package="bar"} - bar2 = {version="2.0", package="bar"} + bar1 = {version="0.1", package="bar"} + bar2 = {version="0.2", package="bar"} "#, ) .file("src/lib.rs", "") @@ -604,9 +712,9 @@ error: package ID specification `baz` did not match any packages ) .run(); - p.cargo("clean -p bar:1.0.0") + p.cargo("clean -p bar:0") .with_stderr( - "warning: version qualifier in `-p bar:1.0.0` is ignored, \ + "warning: version qualifier in `-p bar:0` is ignored, \ cleaning all versions of `bar` found", ) .run(); diff --git a/tests/testsuite/credential_process.rs b/tests/testsuite/credential_process.rs index 7c86943ff05a..2f59e8856a43 100644 --- a/tests/testsuite/credential_process.rs +++ b/tests/testsuite/credential_process.rs @@ -7,63 +7,6 @@ fn toml_bin(proj: &Project, name: &str) -> String { proj.bin(name).display().to_string().replace('\\', "\\\\") } -#[cargo_test] -fn gated() { - let _alternative = registry::RegistryBuilder::new() - .alternative() - .no_configure_token() - .build(); - - let cratesio = registry::RegistryBuilder::new() - .no_configure_token() - .build(); - - let p = project() - .file( - ".cargo/config", - r#" - [registry] - credential-provider = ["false"] - "#, - ) - .file("Cargo.toml", &basic_manifest("foo", "1.0.0")) - .file("src/lib.rs", "") - .build(); - - p.cargo("publish --no-verify") - .replace_crates_io(cratesio.index_url()) - .masquerade_as_nightly_cargo(&["credential-process"]) - .with_status(101) - .with_stderr( - "\ -[UPDATING] [..] -[ERROR] no token found, please run `cargo login` -or use environment variable CARGO_REGISTRY_TOKEN -", - ) - .run(); - - p.change_file( - ".cargo/config", - r#" - [registry.alternative] - credential-process = "false" - "#, - ); - - p.cargo("publish --no-verify --registry alternative") - .masquerade_as_nightly_cargo(&["credential-process"]) - .with_status(101) - .with_stderr( - "\ -[UPDATING] [..] -[ERROR] no token found for `alternative`, please run `cargo login --registry alternative` -or use environment variable CARGO_REGISTRIES_ALTERNATIVE_TOKEN -", - ) - .run(); -} - /// Setup for a test that will issue a command that needs to fetch a token. /// /// This does the following: @@ -125,8 +68,7 @@ fn publish() { // Checks that credential-process is used for `cargo publish`. let (p, _t) = get_token_test(); - p.cargo("publish --no-verify --registry alternative -Z credential-process -Z registry-auth") - .masquerade_as_nightly_cargo(&["credential-process"]) + p.cargo("publish --no-verify --registry alternative") .with_stderr( r#"[UPDATING] [..] {"v":1,"registry":{"index-url":"[..]","name":"alternative","headers":[..]},"kind":"get","operation":"read"} @@ -151,9 +93,8 @@ fn basic_unsupported() { .credential_provider(&["cargo:token-from-stdout", "false"]) .build(); - cargo_process("login -Z credential-process abcdefg") + cargo_process("login abcdefg") .replace_crates_io(registry.index_url()) - .masquerade_as_nightly_cargo(&["credential-process"]) .with_status(101) .with_stderr( "\ @@ -166,9 +107,8 @@ Caused by: ) .run(); - cargo_process("logout -Z credential-process") + cargo_process("logout") .replace_crates_io(registry.index_url()) - .masquerade_as_nightly_cargo(&["credential-process"]) .with_status(101) .with_stderr( "\ @@ -192,8 +132,7 @@ fn login() { ]) .build(); - cargo_process("login -Z credential-process abcdefg -- cmd3 --cmd4") - .masquerade_as_nightly_cargo(&["credential-process"]) + cargo_process("login abcdefg -- cmd3 --cmd4") .replace_crates_io(registry.index_url()) .with_stderr( r#"[UPDATING] [..] @@ -213,8 +152,7 @@ fn logout() { )]) .build(); - cargo_process("logout -Z credential-process") - .masquerade_as_nightly_cargo(&["credential-process"]) + cargo_process("logout") .replace_crates_io(server.index_url()) .with_stderr( r#"{"v":1,"registry":{"index-url":"https://github.com/rust-lang/crates.io-index","name":"crates-io"},"kind":"logout"} @@ -227,8 +165,7 @@ fn logout() { fn yank() { let (p, _t) = get_token_test(); - p.cargo("yank --version 0.1.0 --registry alternative -Zcredential-process -Zregistry-auth") - .masquerade_as_nightly_cargo(&["credential-process"]) + p.cargo("yank --version 0.1.0 --registry alternative") .with_stderr( r#"[UPDATING] [..] {"v":1,"registry":{"index-url":"[..]","name":"alternative","headers":[..]},"kind":"get","operation":"read"} @@ -243,8 +180,7 @@ fn yank() { fn owner() { let (p, _t) = get_token_test(); - p.cargo("owner --add username --registry alternative -Zcredential-process -Zregistry-auth") - .masquerade_as_nightly_cargo(&["credential-process"]) + p.cargo("owner --add username --registry alternative") .with_stderr( r#"[UPDATING] [..] {"v":1,"registry":{"index-url":"[..]","name":"alternative","headers":[..]},"kind":"get","operation":"read"} @@ -278,8 +214,7 @@ fn invalid_token_output() { .file("src/lib.rs", "") .build(); - p.cargo("publish --no-verify --registry alternative -Z credential-process") - .masquerade_as_nightly_cargo(&["credential-process"]) + p.cargo("publish --no-verify --registry alternative") .with_status(101) .with_stderr( "\ @@ -333,8 +268,7 @@ fn not_found() { .build(); // should not suggest a _TOKEN environment variable since the cargo:token provider isn't available. - cargo_process("install -v foo -Zcredential-process -Zregistry-auth") - .masquerade_as_nightly_cargo(&["credential-process", "registry-auth"]) + cargo_process("install -v foo") .replace_crates_io(registry.index_url()) .with_status(101) .with_stderr( @@ -373,8 +307,7 @@ fn all_not_found() { .unwrap(); // should not suggest a _TOKEN environment variable since the cargo:token provider isn't available. - cargo_process("install -v foo -Zcredential-process -Zregistry-auth") - .masquerade_as_nightly_cargo(&["credential-process", "registry-auth"]) + cargo_process("install -v foo") .replace_crates_io(server.index_url()) .with_status(101) .with_stderr( @@ -413,8 +346,7 @@ fn all_not_supported() { ) .unwrap(); - cargo_process("install -v foo -Zcredential-process -Zregistry-auth") - .masquerade_as_nightly_cargo(&["credential-process", "registry-auth"]) + cargo_process("install -v foo") .replace_crates_io(server.index_url()) .with_status(101) .with_stderr( @@ -461,8 +393,7 @@ fn multiple_providers() { ) .unwrap(); - cargo_process("login -Z credential-process -v abcdefg") - .masquerade_as_nightly_cargo(&["credential-process"]) + cargo_process("login -v abcdefg") .replace_crates_io(server.index_url()) .with_stderr( r#"[UPDATING] [..] @@ -481,8 +412,8 @@ fn both_token_and_provider() { .credential_provider(&["cargo:paseto"]) .build(); - cargo_process("login -Z credential-process -Z asymmetric-token") - .masquerade_as_nightly_cargo(&["credential-process", "asymmetric-token"]) + cargo_process("login -Z asymmetric-token") + .masquerade_as_nightly_cargo(&["asymmetric-token"]) .replace_crates_io(server.index_url()) .with_stderr( r#"[UPDATING] [..] @@ -508,8 +439,7 @@ fn registry_provider_overrides_global() { ) .unwrap(); - cargo_process("login -Z credential-process -v abcdefg") - .masquerade_as_nightly_cargo(&["credential-process"]) + cargo_process("login -v abcdefg") .env("CARGO_REGISTRY_CREDENTIAL_PROVIDER", "cargo:token") .replace_crates_io(server.index_url()) .with_stderr( @@ -619,8 +549,7 @@ You may press ctrl-c [..] // The output should contain two JSON messages from the provider in boths cases: // The first because the credential is expired, the second because the provider // indicated that the token was non-operation-independent. - p.cargo("publish -Z credential-process --registry alternative --no-verify") - .masquerade_as_nightly_cargo(&["credential-process"]) + p.cargo("publish --registry alternative --no-verify") .with_stderr(output) .run(); @@ -636,8 +565,7 @@ You may press ctrl-c [..] ), ); - p.cargo("publish -Z credential-process --registry alternative --no-verify") - .masquerade_as_nightly_cargo(&["credential-process"]) + p.cargo("publish --registry alternative --no-verify") .with_stderr(output) .run(); } @@ -687,8 +615,7 @@ fn basic_provider() { .build(); Package::new("bar", "0.0.1").alternative(true).publish(); - p.cargo("check -Z credential-process -Z registry-auth") - .masquerade_as_nightly_cargo(&["credential-process", "registry-auth"]) + p.cargo("check") .with_stderr( "\ [UPDATING] `alternative` index @@ -731,8 +658,7 @@ fn unsupported_version() { .credential_provider(&[&provider]) .build(); - cargo_process("login -Z credential-process abcdefg") - .masquerade_as_nightly_cargo(&["credential-process"]) + cargo_process("login abcdefg") .replace_crates_io(registry.index_url()) .with_status(101) .with_stderr( @@ -764,8 +690,7 @@ fn alias_builtin_warning() { ) .unwrap(); - cargo_process("login -Z credential-process abcdefg") - .masquerade_as_nightly_cargo(&["credential-process"]) + cargo_process("login abcdefg") .replace_crates_io(registry.index_url()) .with_stderr( r#"[UPDATING] [..] diff --git a/tests/testsuite/git.rs b/tests/testsuite/git.rs index 7ed9b546cdeb..e27315346c22 100644 --- a/tests/testsuite/git.rs +++ b/tests/testsuite/git.rs @@ -1887,8 +1887,8 @@ fn update_ambiguous() { "\ [ERROR] There are multiple `bar` packages in your project, and the specification `bar` \ is ambiguous. -Please re-run this command with `-p ` where `` is one of the \ -following: +Please re-run this command with one of the \ +following specifications: bar@0.[..].0 bar@0.[..].0 ", diff --git a/tests/testsuite/login.rs b/tests/testsuite/login.rs index 9b84c541e477..0eec734cebb2 100644 --- a/tests/testsuite/login.rs +++ b/tests/testsuite/login.rs @@ -197,8 +197,8 @@ fn bad_asymmetric_token_args() { .build(); // These cases are kept brief as the implementation is covered by clap, so this is only smoke testing that we have clap configured correctly. - cargo_process("login -Zcredential-process -Zasymmetric-token -- --key-subject") - .masquerade_as_nightly_cargo(&["credential-process", "asymmetric-token"]) + cargo_process("login -Zasymmetric-token -- --key-subject") + .masquerade_as_nightly_cargo(&["asymmetric-token"]) .replace_crates_io(registry.index_url()) .with_stderr_contains( " error: a value is required for '--key-subject ' but none was supplied", @@ -228,8 +228,8 @@ fn login_with_asymmetric_token_and_subject_on_stdin() { .no_configure_token() .build(); let credentials = credentials_toml(); - cargo_process("login -v -Z credential-process -Z asymmetric-token -- --key-subject=foo") - .masquerade_as_nightly_cargo(&["credential-process"]) + cargo_process("login -v -Z asymmetric-token -- --key-subject=foo") + .masquerade_as_nightly_cargo(&["asymmetric-token"]) .replace_crates_io(registry.index_url()) .with_stderr_contains( "\ @@ -286,8 +286,8 @@ fn login_with_asymmetric_token_on_stdin() { .no_configure_token() .build(); let credentials = credentials_toml(); - cargo_process("login -vZ credential-process -Z asymmetric-token --registry alternative") - .masquerade_as_nightly_cargo(&["credential-process", "asymmetric-token"]) + cargo_process("login -v -Z asymmetric-token --registry alternative") + .masquerade_as_nightly_cargo(&["asymmetric-token"]) .with_stderr( "\ [UPDATING] [..] @@ -308,8 +308,8 @@ fn login_with_generate_asymmetric_token() { .no_configure_token() .build(); let credentials = credentials_toml(); - cargo_process("login -Z credential-process -Z asymmetric-token --registry alternative") - .masquerade_as_nightly_cargo(&["credential-process", "asymmetric-token"]) + cargo_process("login -Z asymmetric-token --registry alternative") + .masquerade_as_nightly_cargo(&["asymmetric-token"]) .with_stderr("[UPDATING] `alternative` index\nk3.public.[..]") .run(); let credentials = fs::read_to_string(&credentials).unwrap(); diff --git a/tests/testsuite/pkgid.rs b/tests/testsuite/pkgid.rs index 1857f43b1857..88d991e80d66 100644 --- a/tests/testsuite/pkgid.rs +++ b/tests/testsuite/pkgid.rs @@ -151,25 +151,19 @@ fn multiple_versions() { .with_status(101) .with_stderr( "\ -error: invalid package ID specification: `two-ver@0` - -Did you mean `two-ver`? - -Caused by: - cannot parse '0' as a SemVer version +error: There are multiple `two-ver` packages in your project, and the specification `two-ver@0` is ambiguous. +Please re-run this command with one of the following specifications: + two-ver@0.1.0 + two-ver@0.2.0 ", ) .run(); // Incomplete version. p.cargo("pkgid two-ver@0.2") - .with_status(101) - .with_stderr( + .with_stdout( "\ -error: invalid package ID specification: `two-ver@0.2` - -Caused by: - cannot parse '0.2' as a SemVer version +https://github.com/rust-lang/crates.io-index#two-ver@0.2.0 ", ) .run(); @@ -180,7 +174,7 @@ Caused by: .with_stderr( "\ error: There are multiple `two-ver` packages in your project, and the specification `two-ver` is ambiguous. -Please re-run this command with `-p ` where `` is one of the following: +Please re-run this command with one of the following specifications: two-ver@0.1.0 two-ver@0.2.0 ", diff --git a/tests/testsuite/profile_overrides.rs b/tests/testsuite/profile_overrides.rs index dc9bafba184c..65d9f3b51925 100644 --- a/tests/testsuite/profile_overrides.rs +++ b/tests/testsuite/profile_overrides.rs @@ -267,6 +267,60 @@ found package specs: bar, bar@0.5.0", .run(); } +#[cargo_test] +fn profile_override_spec_with_version() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + + [dependencies] + bar = { path = "bar" } + + [profile.dev.package."bar:0.5.0"] + codegen-units = 2 + "#, + ) + .file("src/lib.rs", "") + .file("bar/Cargo.toml", &basic_lib_manifest("bar")) + .file("bar/src/lib.rs", "") + .build(); + + p.cargo("check -v") + .with_stderr_contains("[RUNNING] `rustc [..]bar/src/lib.rs [..] -C codegen-units=2 [..]") + .run(); +} + +#[cargo_test] +fn profile_override_spec_with_partial_version() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + + [dependencies] + bar = { path = "bar" } + + [profile.dev.package."bar:0.5"] + codegen-units = 2 + "#, + ) + .file("src/lib.rs", "") + .file("bar/Cargo.toml", &basic_lib_manifest("bar")) + .file("bar/src/lib.rs", "") + .build(); + + p.cargo("check -v") + .with_stderr_contains("[RUNNING] `rustc [..]bar/src/lib.rs [..] -C codegen-units=2 [..]") + .run(); +} + #[cargo_test] fn profile_override_spec() { let p = project() diff --git a/tests/testsuite/publish.rs b/tests/testsuite/publish.rs index 91e212fe2596..9842d526fd89 100644 --- a/tests/testsuite/publish.rs +++ b/tests/testsuite/publish.rs @@ -921,6 +921,48 @@ You may press ctrl-c [..] ); } +#[cargo_test] +fn publish_failed_with_index_and_only_allowed_registry() { + let registry = RegistryBuilder::new() + .http_api() + .http_index() + .alternative() + .build(); + + let p = project().build(); + + let _ = repo(&paths::root().join("foo")) + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + documentation = "foo" + homepage = "foo" + repository = "foo" + publish = ["alternative"] + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("publish") + .arg("--index") + .arg(registry.index_url().as_str()) + .with_status(101) + .with_stderr( + "\ +[NOTE] Found `alternative` as only allowed registry. Publishing to it automatically. +[ERROR] command-line argument --index requires --token to be specified +", + ) + .run(); +} + #[cargo_test] fn publish_fail_with_no_registry_specified() { let p = project().build(); diff --git a/tests/testsuite/registry_auth.rs b/tests/testsuite/registry_auth.rs index 9f127669df3f..df2bdf565d79 100644 --- a/tests/testsuite/registry_auth.rs +++ b/tests/testsuite/registry_auth.rs @@ -6,10 +6,12 @@ use cargo_test_support::{project, Execs, Project}; fn cargo(p: &Project, s: &str) -> Execs { let mut e = p.cargo(s); - e.masquerade_as_nightly_cargo(&["registry-auth", "credential-process", "asymmetric-token"]) - .arg("-Zregistry-auth") - .arg("-Zcredential-process") + e.masquerade_as_nightly_cargo(&["asymmetric-token"]) .arg("-Zasymmetric-token"); + e.env( + "CARGO_REGISTRY_GLOBAL_CREDENTIAL_PROVIDERS", + "cargo:paseto cargo:token", + ); e } @@ -44,7 +46,7 @@ static SUCCESS_OUTPUT: &'static str = "\ "; #[cargo_test] -fn requires_nightly() { +fn requires_credential_provider() { let _registry = RegistryBuilder::new() .alternative() .auth_required() @@ -56,14 +58,14 @@ fn requires_nightly() { .with_status(101) .with_stderr( r#"[UPDATING] `alternative` index -[DOWNLOADING] crates ... -error: failed to download from `[..]/dl/bar/0.0.1/download` +error: failed to download `bar v0.0.1 (registry `alternative`)` Caused by: - failed to get successful HTTP response from `[..]` (127.0.0.1), got 401 - body: - Unauthorized message from server. -"#, + unable to get packages from source + +Caused by: + authenticated registries require a credential-provider to be configured + see https://doc.rust-lang.org/cargo/reference/registry-authentication.html for details"#, ) .run(); } diff --git a/tests/testsuite/search.rs b/tests/testsuite/search.rs index dd54a5bcf4bf..4c3155c8fb2b 100644 --- a/tests/testsuite/search.rs +++ b/tests/testsuite/search.rs @@ -173,8 +173,7 @@ fn colored_results() { fn auth_required_failure() { let server = setup().auth_required().no_configure_token().build(); - cargo_process("-Zregistry-auth search postgres") - .masquerade_as_nightly_cargo(&["registry-auth"]) + cargo_process("search postgres") .replace_crates_io(server.index_url()) .with_status(101) .with_stderr_contains("[ERROR] no token found, please run `cargo login`") @@ -185,8 +184,7 @@ fn auth_required_failure() { fn auth_required() { let server = setup().auth_required().build(); - cargo_process("-Zregistry-auth search postgres") - .masquerade_as_nightly_cargo(&["registry-auth"]) + cargo_process("search postgres") .replace_crates_io(server.index_url()) .with_stdout_contains(SEARCH_RESULTS) .run(); diff --git a/tests/testsuite/tree.rs b/tests/testsuite/tree.rs index e2e74c4f954c..16cc37f7e56c 100644 --- a/tests/testsuite/tree.rs +++ b/tests/testsuite/tree.rs @@ -1675,7 +1675,7 @@ fn ambiguous_name() { .with_stderr_contains( "\ error: There are multiple `dep` packages in your project, and the specification `dep` is ambiguous. -Please re-run this command with `-p ` where `` is one of the following: +Please re-run this command with one of the following specifications: dep@1.0.0 dep@2.0.0 ",