diff --git a/src/bin/cargo/commands/run.rs b/src/bin/cargo/commands/run.rs index 9db28bc626c..1803e25fcf1 100644 --- a/src/bin/cargo/commands/run.rs +++ b/src/bin/cargo/commands/run.rs @@ -85,6 +85,7 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult { ops::run(&ws, &compile_opts, &values_os(args, "args")).map_err(|err| to_run_error(config, err)) } +/// See also `util/toml/mod.rs`s `is_embedded` pub fn is_manifest_command(arg: &str) -> bool { let path = Path::new(arg); 1 < path.components().count() @@ -98,15 +99,7 @@ pub fn exec_manifest_command(config: &Config, cmd: &str, args: &[OsString]) -> C } let manifest_path = Path::new(cmd); - let manifest_path = config.cwd().join(manifest_path); - let manifest_path = cargo_util::paths::normalize_path(&manifest_path); - if !manifest_path.exists() { - return Err(anyhow::format_err!( - "manifest path `{}` does not exist", - manifest_path.display() - ) - .into()); - } + let manifest_path = root_manifest(Some(manifest_path), config)?; let mut ws = Workspace::new(&manifest_path, config)?; if config.cli_unstable().avoid_dev_deps { ws.set_require_optional_deps(false); diff --git a/src/cargo/util/command_prelude.rs b/src/cargo/util/command_prelude.rs index f57803e0baa..46ed7dd7cd1 100644 --- a/src/cargo/util/command_prelude.rs +++ b/src/cargo/util/command_prelude.rs @@ -15,6 +15,7 @@ use crate::CargoResult; use anyhow::bail; use cargo_util::paths; use std::ffi::{OsStr, OsString}; +use std::path::Path; use std::path::PathBuf; pub use crate::core::compiler::CompileMode; @@ -339,22 +340,7 @@ pub trait ArgMatchesExt { } fn root_manifest(&self, config: &Config) -> CargoResult { - if let Some(path) = self.value_of_path("manifest-path", config) { - // In general, we try to avoid normalizing paths in Cargo, - // but in this particular case we need it to fix #3586. - let path = paths::normalize_path(&path); - if !path.ends_with("Cargo.toml") { - anyhow::bail!("the manifest-path must be a path to a Cargo.toml file") - } - if !path.exists() { - anyhow::bail!( - "manifest path `{}` does not exist", - self._value_of("manifest-path").unwrap() - ) - } - return Ok(path); - } - find_root_manifest_for_wd(config.cwd()) + root_manifest(self._value_of("manifest-path").map(Path::new), config) } fn workspace<'a>(&self, config: &'a Config) -> CargoResult> { @@ -792,6 +778,33 @@ pub fn values_os(args: &ArgMatches, name: &str) -> Vec { args._values_of_os(name) } +pub fn root_manifest(manifest_path: Option<&Path>, config: &Config) -> CargoResult { + if let Some(manifest_path) = manifest_path { + let path = config.cwd().join(manifest_path); + // In general, we try to avoid normalizing paths in Cargo, + // but in this particular case we need it to fix #3586. + let path = paths::normalize_path(&path); + if !path.ends_with("Cargo.toml") && !crate::util::toml::is_embedded(&path) { + anyhow::bail!("the manifest-path must be a path to a Cargo.toml file") + } + if !path.exists() { + anyhow::bail!("manifest path `{}` does not exist", manifest_path.display()) + } + if path.is_dir() { + anyhow::bail!( + "manifest path `{}` is a directory but expected a file", + manifest_path.display() + ) + } + if crate::util::toml::is_embedded(&path) && !config.cli_unstable().script { + anyhow::bail!("embedded manifest `{}` requires `-Zscript`", path.display()) + } + Ok(path) + } else { + find_root_manifest_for_wd(config.cwd()) + } +} + #[track_caller] pub fn ignore_unknown(r: Result) -> T { match r { diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index a32f0384b60..51d58f5f5d4 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -74,9 +74,12 @@ pub fn read_manifest( .map_err(|err| ManifestError::new(err, path.into())) } -fn is_embedded(path: &Path) -> bool { +/// See also `bin/cargo/commands/run.rs`s `is_manifest_command` +pub fn is_embedded(path: &Path) -> bool { let ext = path.extension(); - ext.is_none() || ext == Some(OsStr::new("rs")) + ext == Some(OsStr::new("rs")) || + // Provide better errors by not considering directories to be embedded manifests + (ext.is_none() && path.is_file()) } /// Parse an already-loaded `Cargo.toml` as a Cargo manifest. diff --git a/tests/testsuite/script.rs b/tests/testsuite/script.rs index bf4fc75f27b..3b6d2f16e51 100644 --- a/tests/testsuite/script.rs +++ b/tests/testsuite/script.rs @@ -10,6 +10,9 @@ fn main() { println!("bin: {bin}"); println!("args: {args:?}"); } + +#[test] +fn test () {} "#; #[cfg(unix)] @@ -457,6 +460,38 @@ args: [] .run(); } +#[cargo_test] +fn script_like_dir() { + let p = cargo_test_support::project() + .file("script.rs/foo", "something") + .build(); + + p.cargo("-Zscript script.rs") + .masquerade_as_nightly_cargo(&["script"]) + .with_status(101) + .with_stderr( + "\ +error: manifest path `script.rs` is a directory but expected a file +", + ) + .run(); +} + +#[cargo_test] +fn missing_script_rs() { + let p = cargo_test_support::project().build(); + + p.cargo("-Zscript script.rs") + .masquerade_as_nightly_cargo(&["script"]) + .with_status(101) + .with_stderr( + "\ +[ERROR] manifest path `script.rs` does not exist +", + ) + .run(); +} + #[cargo_test] fn test_name_same_as_dependency() { Package::new("script", "1.0.0").publish(); @@ -644,3 +679,423 @@ args: [] assert!(!local_lockfile_path.exists()); } + +#[cargo_test] +fn cmd_check_requires_nightly() { + let script = ECHO_SCRIPT; + let p = cargo_test_support::project() + .file("script.rs", script) + .build(); + + p.cargo("check --manifest-path script.rs") + .with_status(101) + .with_stdout("") + .with_stderr( + "\ +error: embedded manifest `[ROOT]/foo/script.rs` requires `-Zscript` +", + ) + .run(); +} + +#[cargo_test] +fn cmd_check_requires_z_flag() { + let script = ECHO_SCRIPT; + let p = cargo_test_support::project() + .file("script.rs", script) + .build(); + + p.cargo("check --manifest-path script.rs") + .masquerade_as_nightly_cargo(&["script"]) + .with_status(101) + .with_stdout("") + .with_stderr( + "\ +error: embedded manifest `[ROOT]/foo/script.rs` requires `-Zscript` +", + ) + .run(); +} + +#[cargo_test] +fn cmd_check_with_embedded() { + let script = ECHO_SCRIPT; + let p = cargo_test_support::project() + .file("script.rs", script) + .build(); + + p.cargo("-Zscript check --manifest-path script.rs") + .masquerade_as_nightly_cargo(&["script"]) + .with_stdout( + "\ +", + ) + .with_stderr( + "\ +[WARNING] `package.edition` is unspecifiead, defaulting to `2021` +[CHECKING] script v0.0.0 ([ROOT]/foo) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s +", + ) + .run(); +} + +#[cargo_test] +fn cmd_check_with_missing_script_rs() { + let p = cargo_test_support::project().build(); + + p.cargo("-Zscript check --manifest-path script.rs") + .masquerade_as_nightly_cargo(&["script"]) + .with_status(101) + .with_stdout( + "\ +", + ) + .with_stderr( + "\ +[ERROR] manifest path `script.rs` does not exist +", + ) + .run(); +} + +#[cargo_test] +fn cmd_check_with_missing_script() { + let p = cargo_test_support::project().build(); + + p.cargo("-Zscript check --manifest-path script") + .masquerade_as_nightly_cargo(&["script"]) + .with_status(101) + .with_stdout( + "\ +", + ) + .with_stderr( + "\ +[ERROR] the manifest-path must be a path to a Cargo.toml file +", + ) + .run(); +} + +#[cargo_test] +fn cmd_build_with_embedded() { + let script = ECHO_SCRIPT; + let p = cargo_test_support::project() + .file("script.rs", script) + .build(); + + p.cargo("-Zscript build --manifest-path script.rs") + .masquerade_as_nightly_cargo(&["script"]) + .with_stdout( + "\ +", + ) + .with_stderr( + "\ +[WARNING] `package.edition` is unspecifiead, defaulting to `2021` +[COMPILING] script v0.0.0 ([ROOT]/foo) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s +", + ) + .run(); +} + +#[cargo_test] +fn cmd_test_with_embedded() { + let script = ECHO_SCRIPT; + let p = cargo_test_support::project() + .file("script.rs", script) + .build(); + + p.cargo("-Zscript test --manifest-path script.rs") + .masquerade_as_nightly_cargo(&["script"]) + .with_stdout( + " +running 1 test +test test ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in [..]s + +", + ) + .with_stderr( + "\ +[WARNING] `package.edition` is unspecifiead, defaulting to `2021` +[COMPILING] script v0.0.0 ([ROOT]/foo) +[FINISHED] test [unoptimized + debuginfo] target(s) in [..]s +[RUNNING] unittests script.rs ([..]) +", + ) + .run(); +} + +#[cargo_test] +fn cmd_clean_with_embedded() { + let script = ECHO_SCRIPT; + let p = cargo_test_support::project() + .file("script.rs", script) + .build(); + + // Ensure there is something to clean + p.cargo("-Zscript script.rs") + .masquerade_as_nightly_cargo(&["script"]) + .run(); + + p.cargo("-Zscript clean --manifest-path script.rs") + .masquerade_as_nightly_cargo(&["script"]) + .with_stdout( + "\ +", + ) + .with_stderr( + "\ +[WARNING] `package.edition` is unspecifiead, defaulting to `2021` +", + ) + .run(); +} + +#[cargo_test] +fn cmd_generate_lockfile_with_embedded() { + let script = ECHO_SCRIPT; + let p = cargo_test_support::project() + .file("script.rs", script) + .build(); + + p.cargo("-Zscript generate-lockfile --manifest-path script.rs") + .masquerade_as_nightly_cargo(&["script"]) + .with_stdout( + "\ +", + ) + .with_stderr( + "\ +[WARNING] `package.edition` is unspecifiead, defaulting to `2021` +", + ) + .run(); +} + +#[cargo_test] +fn cmd_metadata_with_embedded() { + let script = ECHO_SCRIPT; + let p = cargo_test_support::project() + .file("script.rs", script) + .build(); + + p.cargo("-Zscript metadata --manifest-path script.rs --format-version=1") + .masquerade_as_nightly_cargo(&["script"]) + .with_json( + r#" + { + "packages": [ + { + "authors": [ + ], + "categories": [], + "default_run": null, + "name": "script", + "version": "0.0.0", + "id": "script[..]", + "keywords": [], + "source": null, + "dependencies": [], + "edition": "[..]", + "license": null, + "license_file": null, + "links": null, + "description": null, + "readme": null, + "repository": null, + "rust_version": null, + "homepage": null, + "documentation": null, + "homepage": null, + "documentation": null, + "targets": [ + { + "kind": [ + "bin" + ], + "crate_types": [ + "bin" + ], + "doc": true, + "doctest": false, + "test": true, + "edition": "[..]", + "name": "script", + "src_path": "[..]/script.rs" + } + ], + "features": {}, + "manifest_path": "[..]script.rs", + "metadata": null, + "publish": [] + } + ], + "workspace_members": ["script 0.0.0 (path+file:[..]foo)"], + "workspace_default_members": ["script 0.0.0 (path+file:[..]foo)"], + "resolve": { + "nodes": [ + { + "dependencies": [], + "deps": [], + "features": [], + "id": "script 0.0.0 (path+file:[..]foo)" + } + ], + "root": "script 0.0.0 (path+file:[..]foo)" + }, + "target_directory": "[ROOT]/home/.cargo/target/[..]", + "version": 1, + "workspace_root": "[..]/foo", + "metadata": null + }"#, + ) + .with_stderr( + "\ +[WARNING] `package.edition` is unspecifiead, defaulting to `2021` +", + ) + .run(); +} + +#[cargo_test] +fn cmd_read_manifest_with_embedded() { + let script = ECHO_SCRIPT; + let p = cargo_test_support::project() + .file("script.rs", script) + .build(); + + p.cargo("-Zscript read-manifest --manifest-path script.rs") + .masquerade_as_nightly_cargo(&["script"]) + .with_json( + r#" +{ + "authors": [ + ], + "categories": [], + "default_run": null, + "name":"script", + "readme": null, + "homepage": null, + "documentation": null, + "repository": null, + "rust_version": null, + "version":"0.0.0", + "id":"script[..]0.0.0[..](path+file://[..]/foo)", + "keywords": [], + "license": null, + "license_file": null, + "links": null, + "description": null, + "edition": "[..]", + "source":null, + "dependencies":[], + "targets":[{ + "kind":["bin"], + "crate_types":["bin"], + "doc": true, + "doctest": false, + "test": true, + "edition": "[..]", + "name":"script", + "src_path":"[..]/script.rs" + }], + "features":{}, + "manifest_path":"[..]script.rs", + "metadata": null, + "publish": [] +}"#, + ) + .with_stderr( + "\ +[WARNING] `package.edition` is unspecifiead, defaulting to `2021` +", + ) + .run(); +} + +#[cargo_test] +fn cmd_run_with_embedded() { + let p = cargo_test_support::project() + .file("script.rs", ECHO_SCRIPT) + .build(); + + p.cargo("-Zscript run --manifest-path script.rs") + .masquerade_as_nightly_cargo(&["script"]) + .with_stdout( + r#"bin: [..]/debug/script[EXE] +args: [] +"#, + ) + .with_stderr( + "\ +[WARNING] `package.edition` is unspecifiead, defaulting to `2021` +[COMPILING] script v0.0.0 ([ROOT]/foo) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s +[RUNNING] `[..]/debug/script[EXE]` +", + ) + .run(); +} + +#[cargo_test] +fn cmd_tree_with_embedded() { + let p = cargo_test_support::project() + .file("script.rs", ECHO_SCRIPT) + .build(); + + p.cargo("-Zscript tree --manifest-path script.rs") + .masquerade_as_nightly_cargo(&["script"]) + .with_stdout( + "\ +script v0.0.0 ([ROOT]/foo) +", + ) + .with_stderr( + "\ +[WARNING] `package.edition` is unspecifiead, defaulting to `2021` +", + ) + .run(); +} + +#[cargo_test] +fn cmd_update_with_embedded() { + let p = cargo_test_support::project() + .file("script.rs", ECHO_SCRIPT) + .build(); + + p.cargo("-Zscript update --manifest-path script.rs") + .masquerade_as_nightly_cargo(&["script"]) + .with_stdout( + "\ +", + ) + .with_stderr( + "\ +[WARNING] `package.edition` is unspecifiead, defaulting to `2021` +", + ) + .run(); +} + +#[cargo_test] +fn cmd_verify_project_with_embedded() { + let p = cargo_test_support::project() + .file("script.rs", ECHO_SCRIPT) + .build(); + + p.cargo("-Zscript verify-project --manifest-path script.rs") + .masquerade_as_nightly_cargo(&["script"]) + .with_json(r#"{"success":"true"}"#) + .with_stderr( + "\ +[WARNING] `package.edition` is unspecifiead, defaulting to `2021` +", + ) + .run(); +}