Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Allow embedded manifests in all commands #12289

Merged
merged 8 commits into from
Jun 21, 2023
11 changes: 2 additions & 9 deletions src/bin/cargo/commands/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() || path.extension() == Some(OsStr::new("rs"))
Expand All @@ -96,15 +97,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);
Expand Down
45 changes: 29 additions & 16 deletions src/cargo/util/command_prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -339,22 +340,7 @@ pub trait ArgMatchesExt {
}

fn root_manifest(&self, config: &Config) -> CargoResult<PathBuf> {
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<Workspace<'a>> {
Expand Down Expand Up @@ -792,6 +778,33 @@ pub fn values_os(args: &ArgMatches, name: &str) -> Vec<OsString> {
args._values_of_os(name)
}

pub fn root_manifest(manifest_path: Option<&Path>, config: &Config) -> CargoResult<PathBuf> {
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<T: Default>(r: Result<T, clap::parser::MatchesError>) -> T {
match r {
Expand Down
7 changes: 5 additions & 2 deletions src/cargo/util/toml/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")) ||
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Should we always verify that embedded manifest is a file?
  • Can we have some tests around this? Like failure when script.rs is a directory.
  • (Not a blocker) Is it worth having a better error message for each failed case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we always verify that embedded manifest is a file?

We have a separate, more specific error check for that.

(Not a blocker) Is it worth having a better error message for each failed case?

What do you have in mind?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • When missing file at manifest-path, it shows
    error: the manifest-path must be a path to a Cargo.toml file
    
  • When script.rs is a directory, it shows
    error: failed to read `/projects/cargo/test.rs`
    
    Caused by:
      Is a directory (os error 21)
    

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When script.rs is a directory, it shows

This is true for Cargo.toml as well. We can more generally improve it though that is unrelated to this PR

// 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.
Expand Down
Loading