diff --git a/src/bin/cargo/commands/remove.rs b/src/bin/cargo/commands/remove.rs index 833fd00c549..d65410618c7 100644 --- a/src/bin/cargo/commands/remove.rs +++ b/src/bin/cargo/commands/remove.rs @@ -161,8 +161,7 @@ fn parse_section(args: &ArgMatches) -> DepTable { /// Clean up the workspace.dependencies, profile, patch, and replace sections of the root manifest /// by removing dependencies which no longer have a reference to them. fn gc_workspace(workspace: &Workspace<'_>) -> CargoResult<()> { - let mut manifest: toml_edit::DocumentMut = - cargo_util::paths::read(workspace.root_manifest())?.parse()?; + let mut workspace_manifest = LocalManifest::try_new(workspace.root_manifest())?; let mut is_modified = true; let members = workspace @@ -177,8 +176,8 @@ fn gc_workspace(workspace: &Workspace<'_>) -> CargoResult<()> { let mut dependencies = members .into_iter() - .flat_map(|(manifest, unstable_features)| { - manifest + .flat_map(|(member_manifest, unstable_features)| { + member_manifest .get_sections() .into_iter() .flat_map(move |(_, table)| { @@ -190,7 +189,7 @@ fn gc_workspace(workspace: &Workspace<'_>) -> CargoResult<()> { Dependency::from_toml( workspace.gctx(), workspace.root(), - &manifest.path, + &member_manifest.path, &unstable_features, key, item, @@ -203,7 +202,8 @@ fn gc_workspace(workspace: &Workspace<'_>) -> CargoResult<()> { // Clean up the workspace.dependencies section and replace instances of // workspace dependencies with their definitions - if let Some(toml_edit::Item::Table(deps_table)) = manifest + if let Some(toml_edit::Item::Table(deps_table)) = workspace_manifest + .data .get_mut("workspace") .and_then(|t| t.get_mut("dependencies")) { @@ -246,7 +246,9 @@ fn gc_workspace(workspace: &Workspace<'_>) -> CargoResult<()> { // Example tables: // - profile.dev.package.foo // - profile.release.package."foo:2.1.0" - if let Some(toml_edit::Item::Table(profile_section_table)) = manifest.get_mut("profile") { + if let Some(toml_edit::Item::Table(profile_section_table)) = + workspace_manifest.data.get_mut("profile") + { profile_section_table.set_implicit(true); for (_, item) in profile_section_table.iter_mut() { @@ -280,7 +282,7 @@ fn gc_workspace(workspace: &Workspace<'_>) -> CargoResult<()> { } // Clean up the replace section - if let Some(toml_edit::Item::Table(table)) = manifest.get_mut("replace") { + if let Some(toml_edit::Item::Table(table)) = workspace_manifest.data.get_mut("replace") { table.set_implicit(true); for (key, item) in table.iter_mut() { @@ -296,10 +298,7 @@ fn gc_workspace(workspace: &Workspace<'_>) -> CargoResult<()> { } if is_modified { - cargo_util::paths::write_atomic( - workspace.root_manifest(), - manifest.to_string().as_bytes(), - )?; + workspace_manifest.write()?; } Ok(()) @@ -340,12 +339,13 @@ fn spec_has_match( /// Removes unused patches from the manifest fn gc_unused_patches(workspace: &Workspace<'_>, resolve: &Resolve) -> CargoResult { - let mut manifest: toml_edit::DocumentMut = - cargo_util::paths::read(workspace.root_manifest())?.parse()?; + let mut workspace_manifest = LocalManifest::try_new(workspace.root_manifest())?; let mut modified = false; // Clean up the patch section - if let Some(toml_edit::Item::Table(patch_section_table)) = manifest.get_mut("patch") { + if let Some(toml_edit::Item::Table(patch_section_table)) = + workspace_manifest.data.get_mut("patch") + { patch_section_table.set_implicit(true); for (_, item) in patch_section_table.iter_mut() { @@ -383,7 +383,7 @@ fn gc_unused_patches(workspace: &Workspace<'_>, resolve: &Resolve) -> CargoResul } if modified { - cargo_util::paths::write(workspace.root_manifest(), manifest.to_string().as_bytes())?; + workspace_manifest.write()?; } Ok(modified) diff --git a/src/cargo/util/toml/embedded.rs b/src/cargo/util/toml/embedded.rs index 155b54cb18c..126188588b2 100644 --- a/src/cargo/util/toml/embedded.rs +++ b/src/cargo/util/toml/embedded.rs @@ -21,9 +21,9 @@ pub(super) fn expand_manifest( path: &std::path::Path, gctx: &GlobalContext, ) -> CargoResult { - let source = split_source(content)?; - if let Some(frontmatter) = source.frontmatter { - match source.info { + let source = ScriptSource::parse(content)?; + if let Some(frontmatter) = source.frontmatter() { + match source.info() { Some("cargo") | None => {} Some(other) => { if let Some(remainder) = other.strip_prefix("cargo,") { @@ -50,7 +50,7 @@ pub(super) fn expand_manifest( ) .into_path_unlocked(); let mut hacked_source = String::new(); - if let Some(shebang) = source.shebang { + if let Some(shebang) = source.shebang() { writeln!(hacked_source, "{shebang}")?; } writeln!(hacked_source)?; // open @@ -58,7 +58,7 @@ pub(super) fn expand_manifest( writeln!(hacked_source)?; } writeln!(hacked_source)?; // close - writeln!(hacked_source, "{}", source.content)?; + writeln!(hacked_source, "{}", source.content())?; if let Some(parent) = hacked_path.parent() { cargo_util::paths::create_dir_all(parent)?; } @@ -189,94 +189,112 @@ fn sanitize_name(name: &str) -> String { } #[derive(Debug)] -struct Source<'s> { +pub struct ScriptSource<'s> { shebang: Option<&'s str>, info: Option<&'s str>, frontmatter: Option<&'s str>, content: &'s str, } -fn split_source(input: &str) -> CargoResult> { - let mut source = Source { - shebang: None, - info: None, - frontmatter: None, - content: input, - }; +impl<'s> ScriptSource<'s> { + pub fn parse(input: &'s str) -> CargoResult { + let mut source = Self { + shebang: None, + info: None, + frontmatter: None, + content: input, + }; + + // See rust-lang/rust's compiler/rustc_lexer/src/lib.rs's `strip_shebang` + // Shebang must start with `#!` literally, without any preceding whitespace. + // For simplicity we consider any line starting with `#!` a shebang, + // regardless of restrictions put on shebangs by specific platforms. + if let Some(rest) = source.content.strip_prefix("#!") { + // Ok, this is a shebang but if the next non-whitespace token is `[`, + // then it may be valid Rust code, so consider it Rust code. + if rest.trim_start().starts_with('[') { + return Ok(source); + } - // See rust-lang/rust's compiler/rustc_lexer/src/lib.rs's `strip_shebang` - // Shebang must start with `#!` literally, without any preceding whitespace. - // For simplicity we consider any line starting with `#!` a shebang, - // regardless of restrictions put on shebangs by specific platforms. - if let Some(rest) = source.content.strip_prefix("#!") { - // Ok, this is a shebang but if the next non-whitespace token is `[`, - // then it may be valid Rust code, so consider it Rust code. - if rest.trim_start().starts_with('[') { - return Ok(source); + // No other choice than to consider this a shebang. + let newline_end = source + .content + .find('\n') + .map(|pos| pos + 1) + .unwrap_or(source.content.len()); + let (shebang, content) = source.content.split_at(newline_end); + source.shebang = Some(shebang); + source.content = content; } - // No other choice than to consider this a shebang. - let newline_end = source - .content - .find('\n') - .map(|pos| pos + 1) + const FENCE_CHAR: char = '-'; + + let mut trimmed_content = source.content; + while !trimmed_content.is_empty() { + let c = trimmed_content; + let c = c.trim_start_matches([' ', '\t']); + let c = c.trim_start_matches(['\r', '\n']); + if c == trimmed_content { + break; + } + trimmed_content = c; + } + let fence_end = trimmed_content + .char_indices() + .find_map(|(i, c)| (c != FENCE_CHAR).then_some(i)) .unwrap_or(source.content.len()); - let (shebang, content) = source.content.split_at(newline_end); - source.shebang = Some(shebang); + let (fence_pattern, rest) = match fence_end { + 0 => { + return Ok(source); + } + 1 | 2 => { + anyhow::bail!( + "found {fence_end} `{FENCE_CHAR}` in rust frontmatter, expected at least 3" + ) + } + _ => trimmed_content.split_at(fence_end), + }; + let (info, content) = rest.split_once("\n").unwrap_or((rest, "")); + let info = info.trim(); + if !info.is_empty() { + source.info = Some(info); + } source.content = content; - } - const FENCE_CHAR: char = '-'; + let Some((frontmatter, content)) = source.content.split_once(fence_pattern) else { + anyhow::bail!("no closing `{fence_pattern}` found for frontmatter"); + }; + source.frontmatter = Some(frontmatter); + source.content = content; - let mut trimmed_content = source.content; - while !trimmed_content.is_empty() { - let c = trimmed_content; - let c = c.trim_start_matches([' ', '\t']); - let c = c.trim_start_matches(['\r', '\n']); - if c == trimmed_content { - break; + let (line, content) = source + .content + .split_once("\n") + .unwrap_or((source.content, "")); + let line = line.trim(); + if !line.is_empty() { + anyhow::bail!("unexpected trailing content on closing fence: `{line}`"); } - trimmed_content = c; + source.content = content; + + Ok(source) } - let fence_end = trimmed_content - .char_indices() - .find_map(|(i, c)| (c != FENCE_CHAR).then_some(i)) - .unwrap_or(source.content.len()); - let (fence_pattern, rest) = match fence_end { - 0 => { - return Ok(source); - } - 1 | 2 => { - anyhow::bail!( - "found {fence_end} `{FENCE_CHAR}` in rust frontmatter, expected at least 3" - ) - } - _ => trimmed_content.split_at(fence_end), - }; - let (info, content) = rest.split_once("\n").unwrap_or((rest, "")); - let info = info.trim(); - if !info.is_empty() { - source.info = Some(info); + + pub fn shebang(&self) -> Option<&'s str> { + self.shebang } - source.content = content; - let Some((frontmatter, content)) = source.content.split_once(fence_pattern) else { - anyhow::bail!("no closing `{fence_pattern}` found for frontmatter"); - }; - source.frontmatter = Some(frontmatter); - source.content = content; - - let (line, content) = source - .content - .split_once("\n") - .unwrap_or((source.content, "")); - let line = line.trim(); - if !line.is_empty() { - anyhow::bail!("unexpected trailing content on closing fence: `{line}`"); + pub fn info(&self) -> Option<&'s str> { + self.info } - source.content = content; - Ok(source) + pub fn frontmatter(&self) -> Option<&'s str> { + self.frontmatter + } + + pub fn content(&self) -> &'s str { + self.content + } } #[cfg(test)] @@ -291,16 +309,16 @@ mod test_expand { fn assert_source(source: &str, expected: impl IntoData) { use std::fmt::Write as _; - let actual = match split_source(source) { + let actual = match ScriptSource::parse(source) { Ok(actual) => actual, Err(err) => panic!("unexpected err: {err}"), }; let mut rendered = String::new(); - write_optional_field(&mut rendered, "shebang", actual.shebang); - write_optional_field(&mut rendered, "info", actual.info); - write_optional_field(&mut rendered, "frontmatter", actual.frontmatter); - writeln!(&mut rendered, "content: {:?}", actual.content).unwrap(); + write_optional_field(&mut rendered, "shebang", actual.shebang()); + write_optional_field(&mut rendered, "info", actual.info()); + write_optional_field(&mut rendered, "frontmatter", actual.frontmatter()); + writeln!(&mut rendered, "content: {:?}", actual.content()).unwrap(); assert_data_eq!(rendered, expected.raw()); } @@ -497,7 +515,7 @@ content: "\nfn main() {}" #[test] fn split_too_few_dashes() { assert_err( - split_source( + ScriptSource::parse( r#"#!/usr/bin/env cargo -- [dependencies] @@ -513,7 +531,7 @@ fn main() {} #[test] fn split_mismatched_dashes() { assert_err( - split_source( + ScriptSource::parse( r#"#!/usr/bin/env cargo --- [dependencies] @@ -529,7 +547,7 @@ fn main() {} #[test] fn split_missing_close() { assert_err( - split_source( + ScriptSource::parse( r#"#!/usr/bin/env cargo --- [dependencies] diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index d8bf63199ac..ab96cdd03b0 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -39,6 +39,8 @@ mod targets; use self::targets::to_targets; +pub use embedded::ScriptSource; + /// See also `bin/cargo/commands/run.rs`s `is_manifest_command` pub fn is_embedded(path: &Path) -> bool { let ext = path.extension(); diff --git a/src/cargo/util/toml_mut/dependency.rs b/src/cargo/util/toml_mut/dependency.rs index 78cc58a29b7..6d693d6dda3 100644 --- a/src/cargo/util/toml_mut/dependency.rs +++ b/src/cargo/util/toml_mut/dependency.rs @@ -1252,6 +1252,8 @@ mod tests { let mut local = LocalManifest { path: crate_root.clone(), manifest, + embedded: None, + raw: toml.to_owned(), }; assert_eq!(local.manifest.to_string(), toml); let gctx = GlobalContext::default().unwrap(); diff --git a/src/cargo/util/toml_mut/manifest.rs b/src/cargo/util/toml_mut/manifest.rs index 98033531e3a..79fd24029de 100644 --- a/src/cargo/util/toml_mut/manifest.rs +++ b/src/cargo/util/toml_mut/manifest.rs @@ -11,6 +11,7 @@ use crate::core::dependency::DepKind; use crate::core::{FeatureValue, Features, Workspace}; use crate::util::closest; use crate::util::interning::InternedString; +use crate::util::toml::{is_embedded, ScriptSource}; use crate::{CargoResult, GlobalContext}; /// Dependency table to add deps to. @@ -245,6 +246,10 @@ pub struct LocalManifest { pub path: PathBuf, /// Manifest contents. pub manifest: Manifest, + /// The raw, unparsed package file + pub raw: String, + /// Edit location for an embedded manifest, if relevant + pub embedded: Option, } impl Deref for LocalManifest { @@ -267,18 +272,56 @@ impl LocalManifest { if !path.is_absolute() { anyhow::bail!("can only edit absolute paths, got {}", path.display()); } - let data = cargo_util::paths::read(&path)?; + let raw = cargo_util::paths::read(&path)?; + let mut data = raw.clone(); + let mut embedded = None; + if is_embedded(path) { + let source = ScriptSource::parse(&data)?; + if let Some(frontmatter) = source.frontmatter() { + embedded = Some(Embedded::exists(&data, frontmatter)); + data = frontmatter.to_owned(); + } else if let Some(shebang) = source.shebang() { + embedded = Some(Embedded::after(&data, shebang)); + data = String::new(); + } else { + embedded = Some(Embedded::start()); + data = String::new(); + } + } let manifest = data.parse().context("Unable to parse Cargo.toml")?; Ok(LocalManifest { manifest, path: path.to_owned(), + raw, + embedded, }) } /// Write changes back to the file. pub fn write(&self) -> CargoResult<()> { - let s = self.manifest.data.to_string(); - let new_contents_bytes = s.as_bytes(); + let mut manifest = self.manifest.data.to_string(); + let raw = match self.embedded.as_ref() { + Some(Embedded::Implicit(start)) => { + if !manifest.ends_with("\n") { + manifest.push_str("\n"); + } + let fence = "---\n"; + let prefix = &self.raw[0..*start]; + let suffix = &self.raw[*start..]; + let empty_line = if prefix.is_empty() { "\n" } else { "" }; + format!("{prefix}{fence}{manifest}{fence}{empty_line}{suffix}") + } + Some(Embedded::Explicit(span)) => { + if !manifest.ends_with("\n") { + manifest.push_str("\n"); + } + let prefix = &self.raw[0..span.start]; + let suffix = &self.raw[span.end..]; + format!("{prefix}{manifest}{suffix}") + } + None => manifest, + }; + let new_contents_bytes = raw.as_bytes(); cargo_util::paths::write_atomic(&self.path, new_contents_bytes) } @@ -531,6 +574,51 @@ impl std::fmt::Display for LocalManifest { } } +/// Edit location for an embedded manifest +#[derive(Clone, Debug)] +pub enum Embedded { + /// Manifest is implicit + /// + /// This is the insert location for a frontmatter + Implicit(usize), + /// Manifest is explicit in a frontmatter + /// + /// This is the span of the frontmatter body + Explicit(std::ops::Range), +} + +impl Embedded { + fn start() -> Self { + Self::Implicit(0) + } + + fn after(input: &str, after: &str) -> Self { + let span = substr_span(input, after); + let end = span.end; + Self::Implicit(end) + } + + fn exists(input: &str, exists: &str) -> Self { + let span = substr_span(input, exists); + Self::Explicit(span) + } +} + +fn substr_span(haystack: &str, needle: &str) -> std::ops::Range { + let haystack_start_ptr = haystack.as_ptr(); + let haystack_end_ptr = haystack[haystack.len()..haystack.len()].as_ptr(); + + let needle_start_ptr = needle.as_ptr(); + let needle_end_ptr = needle[needle.len()..needle.len()].as_ptr(); + + assert!(needle_end_ptr < haystack_end_ptr); + assert!(haystack_start_ptr <= needle_start_ptr); + let start = needle_start_ptr as usize - haystack_start_ptr as usize; + let end = start + needle.len(); + + start..end +} + #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] enum DependencyStatus { None, diff --git a/tests/testsuite/cargo_add/mod.rs b/tests/testsuite/cargo_add/mod.rs index ff910628ee8..ec00ba48c8a 100644 --- a/tests/testsuite/cargo_add/mod.rs +++ b/tests/testsuite/cargo_add/mod.rs @@ -138,6 +138,9 @@ mod rustc_ignore; mod rustc_incompatible; mod rustc_latest; mod rustc_older; +mod script_bare; +mod script_frontmatter; +mod script_shebang; mod sorted_table_with_dotted_item; mod target; mod target_cfg; diff --git a/tests/testsuite/cargo_add/script_bare/in/cargo-test-fixture.rs b/tests/testsuite/cargo_add/script_bare/in/cargo-test-fixture.rs new file mode 100644 index 00000000000..f328e4d9d04 --- /dev/null +++ b/tests/testsuite/cargo_add/script_bare/in/cargo-test-fixture.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/tests/testsuite/cargo_add/script_bare/mod.rs b/tests/testsuite/cargo_add/script_bare/mod.rs new file mode 100644 index 00000000000..ffa6db6c6f8 --- /dev/null +++ b/tests/testsuite/cargo_add/script_bare/mod.rs @@ -0,0 +1,39 @@ +use cargo_test_support::compare::assert_ui; +use cargo_test_support::current_dir; +use cargo_test_support::file; +use cargo_test_support::prelude::*; +use cargo_test_support::str; +use cargo_test_support::Project; + +#[cargo_test] +fn case() { + cargo_test_support::registry::init(); + for ver in [ + "0.1.1+my-package", + "0.2.0+my-package", + "0.2.3+my-package", + "0.4.1+my-package", + "20.0.0+my-package", + "99999.0.0+my-package", + "99999.0.0-alpha.1+my-package", + ] { + cargo_test_support::registry::Package::new("my-package", ver).publish(); + } + + let project = Project::from_template(current_dir!().join("in")); + let project_root = project.root(); + let cwd = &project_root; + + snapbox::cmd::Command::cargo_ui() + .masquerade_as_nightly_cargo(&["script"]) + .arg("-Zscript") + .arg("add") + .arg_line("--manifest-path cargo-test-fixture.rs my-package") + .current_dir(cwd) + .assert() + .success() + .stdout_eq(str![""]) + .stderr_eq(file!["stderr.term.svg"]); + + assert_ui().subset_matches(current_dir!().join("out"), &project_root); +} diff --git a/tests/testsuite/cargo_add/script_bare/out/cargo-test-fixture.rs b/tests/testsuite/cargo_add/script_bare/out/cargo-test-fixture.rs new file mode 100644 index 00000000000..caf9ce19959 --- /dev/null +++ b/tests/testsuite/cargo_add/script_bare/out/cargo-test-fixture.rs @@ -0,0 +1,6 @@ +--- +[dependencies] +my-package = "99999.0.0" +--- + +fn main() {} diff --git a/tests/testsuite/cargo_add/script_bare/stderr.term.svg b/tests/testsuite/cargo_add/script_bare/stderr.term.svg new file mode 100644 index 00000000000..b5c72375b88 --- /dev/null +++ b/tests/testsuite/cargo_add/script_bare/stderr.term.svg @@ -0,0 +1,36 @@ + + + + + + + warning: `package.edition` is unspecified, defaulting to `[..]` + + Updating `dummy-registry` index + + Adding my-package v99999.0.0 to dependencies + + warning: `package.edition` is unspecified, defaulting to `[..]` + + Locking 1 package to latest [..]compatible version + + + + + + diff --git a/tests/testsuite/cargo_add/script_frontmatter/in/cargo-test-fixture.rs b/tests/testsuite/cargo_add/script_frontmatter/in/cargo-test-fixture.rs new file mode 100644 index 00000000000..129939352ae --- /dev/null +++ b/tests/testsuite/cargo_add/script_frontmatter/in/cargo-test-fixture.rs @@ -0,0 +1,7 @@ +--- +[package] +edition = "2015" +--- + +fn main() { +} diff --git a/tests/testsuite/cargo_add/script_frontmatter/mod.rs b/tests/testsuite/cargo_add/script_frontmatter/mod.rs new file mode 100644 index 00000000000..ffa6db6c6f8 --- /dev/null +++ b/tests/testsuite/cargo_add/script_frontmatter/mod.rs @@ -0,0 +1,39 @@ +use cargo_test_support::compare::assert_ui; +use cargo_test_support::current_dir; +use cargo_test_support::file; +use cargo_test_support::prelude::*; +use cargo_test_support::str; +use cargo_test_support::Project; + +#[cargo_test] +fn case() { + cargo_test_support::registry::init(); + for ver in [ + "0.1.1+my-package", + "0.2.0+my-package", + "0.2.3+my-package", + "0.4.1+my-package", + "20.0.0+my-package", + "99999.0.0+my-package", + "99999.0.0-alpha.1+my-package", + ] { + cargo_test_support::registry::Package::new("my-package", ver).publish(); + } + + let project = Project::from_template(current_dir!().join("in")); + let project_root = project.root(); + let cwd = &project_root; + + snapbox::cmd::Command::cargo_ui() + .masquerade_as_nightly_cargo(&["script"]) + .arg("-Zscript") + .arg("add") + .arg_line("--manifest-path cargo-test-fixture.rs my-package") + .current_dir(cwd) + .assert() + .success() + .stdout_eq(str![""]) + .stderr_eq(file!["stderr.term.svg"]); + + assert_ui().subset_matches(current_dir!().join("out"), &project_root); +} diff --git a/tests/testsuite/cargo_add/script_frontmatter/out/cargo-test-fixture.rs b/tests/testsuite/cargo_add/script_frontmatter/out/cargo-test-fixture.rs new file mode 100644 index 00000000000..0be369510ac --- /dev/null +++ b/tests/testsuite/cargo_add/script_frontmatter/out/cargo-test-fixture.rs @@ -0,0 +1,10 @@ +--- +[package] +edition = "2015" + +[dependencies] +my-package = "99999.0.0" +--- + +fn main() { +} diff --git a/tests/testsuite/cargo_add/script_frontmatter/stderr.term.svg b/tests/testsuite/cargo_add/script_frontmatter/stderr.term.svg new file mode 100644 index 00000000000..0be687ce10a --- /dev/null +++ b/tests/testsuite/cargo_add/script_frontmatter/stderr.term.svg @@ -0,0 +1,31 @@ + + + + + + + Updating `dummy-registry` index + + Adding my-package v99999.0.0 to dependencies + + Locking 1 package to latest [..]compatible version + + + + + + diff --git a/tests/testsuite/cargo_add/script_shebang/in/cargo-test-fixture.rs b/tests/testsuite/cargo_add/script_shebang/in/cargo-test-fixture.rs new file mode 100644 index 00000000000..627aa3d895f --- /dev/null +++ b/tests/testsuite/cargo_add/script_shebang/in/cargo-test-fixture.rs @@ -0,0 +1,3 @@ +#!/usr/bin/env cargo + +fn main() {} diff --git a/tests/testsuite/cargo_add/script_shebang/mod.rs b/tests/testsuite/cargo_add/script_shebang/mod.rs new file mode 100644 index 00000000000..ffa6db6c6f8 --- /dev/null +++ b/tests/testsuite/cargo_add/script_shebang/mod.rs @@ -0,0 +1,39 @@ +use cargo_test_support::compare::assert_ui; +use cargo_test_support::current_dir; +use cargo_test_support::file; +use cargo_test_support::prelude::*; +use cargo_test_support::str; +use cargo_test_support::Project; + +#[cargo_test] +fn case() { + cargo_test_support::registry::init(); + for ver in [ + "0.1.1+my-package", + "0.2.0+my-package", + "0.2.3+my-package", + "0.4.1+my-package", + "20.0.0+my-package", + "99999.0.0+my-package", + "99999.0.0-alpha.1+my-package", + ] { + cargo_test_support::registry::Package::new("my-package", ver).publish(); + } + + let project = Project::from_template(current_dir!().join("in")); + let project_root = project.root(); + let cwd = &project_root; + + snapbox::cmd::Command::cargo_ui() + .masquerade_as_nightly_cargo(&["script"]) + .arg("-Zscript") + .arg("add") + .arg_line("--manifest-path cargo-test-fixture.rs my-package") + .current_dir(cwd) + .assert() + .success() + .stdout_eq(str![""]) + .stderr_eq(file!["stderr.term.svg"]); + + assert_ui().subset_matches(current_dir!().join("out"), &project_root); +} diff --git a/tests/testsuite/cargo_add/script_shebang/out/cargo-test-fixture.rs b/tests/testsuite/cargo_add/script_shebang/out/cargo-test-fixture.rs new file mode 100644 index 00000000000..2abcbf974e0 --- /dev/null +++ b/tests/testsuite/cargo_add/script_shebang/out/cargo-test-fixture.rs @@ -0,0 +1,7 @@ +#!/usr/bin/env cargo +--- +[dependencies] +my-package = "99999.0.0" +--- + +fn main() {} diff --git a/tests/testsuite/cargo_add/script_shebang/stderr.term.svg b/tests/testsuite/cargo_add/script_shebang/stderr.term.svg new file mode 100644 index 00000000000..b5c72375b88 --- /dev/null +++ b/tests/testsuite/cargo_add/script_shebang/stderr.term.svg @@ -0,0 +1,36 @@ + + + + + + + warning: `package.edition` is unspecified, defaulting to `[..]` + + Updating `dummy-registry` index + + Adding my-package v99999.0.0 to dependencies + + warning: `package.edition` is unspecified, defaulting to `[..]` + + Locking 1 package to latest [..]compatible version + + + + + + diff --git a/tests/testsuite/cargo_remove/last_dep/in/Cargo.toml b/tests/testsuite/cargo_remove/last_dep/in/Cargo.toml new file mode 100644 index 00000000000..5e6cc267d4e --- /dev/null +++ b/tests/testsuite/cargo_remove/last_dep/in/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "cargo-remove-test-fixture" +version = "0.1.0" +edition = "2015" + +[[bin]] +name = "main" +path = "src/main.rs" + +[dependencies] +docopt = "0.6" + +[features] diff --git a/tests/testsuite/cargo_remove/last_dep/in/src/lib.rs b/tests/testsuite/cargo_remove/last_dep/in/src/lib.rs new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/tests/testsuite/cargo_remove/last_dep/in/src/lib.rs @@ -0,0 +1 @@ + diff --git a/tests/testsuite/cargo_remove/last_dep/mod.rs b/tests/testsuite/cargo_remove/last_dep/mod.rs new file mode 100644 index 00000000000..6e287f223ce --- /dev/null +++ b/tests/testsuite/cargo_remove/last_dep/mod.rs @@ -0,0 +1,28 @@ +use cargo_test_support::compare::assert_ui; +use cargo_test_support::current_dir; +use cargo_test_support::file; +use cargo_test_support::prelude::*; +use cargo_test_support::str; +use cargo_test_support::CargoCommandExt; +use cargo_test_support::Project; + +#[cargo_test] +fn case() { + cargo_test_support::registry::init(); + cargo_test_support::registry::Package::new("docopt", "0.6.2+my-package").publish(); + + let project = Project::from_template(current_dir!().join("in")); + let project_root = project.root(); + let cwd = &project_root; + + snapbox::cmd::Command::cargo_ui() + .arg("remove") + .args(["docopt"]) + .current_dir(cwd) + .assert() + .success() + .stdout_eq(str![""]) + .stderr_eq(file!["stderr.term.svg"]); + + assert_ui().subset_matches(current_dir!().join("out"), &project_root); +} diff --git a/tests/testsuite/cargo_remove/last_dep/out/Cargo.toml b/tests/testsuite/cargo_remove/last_dep/out/Cargo.toml new file mode 100644 index 00000000000..ad97bd5ca25 --- /dev/null +++ b/tests/testsuite/cargo_remove/last_dep/out/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "cargo-remove-test-fixture" +version = "0.1.0" +edition = "2015" + +[[bin]] +name = "main" +path = "src/main.rs" + +[features] diff --git a/tests/testsuite/cargo_remove/last_dep/stderr.term.svg b/tests/testsuite/cargo_remove/last_dep/stderr.term.svg new file mode 100644 index 00000000000..51bcb0f99a9 --- /dev/null +++ b/tests/testsuite/cargo_remove/last_dep/stderr.term.svg @@ -0,0 +1,27 @@ + + + + + + + Removing docopt from dependencies + + + + + + diff --git a/tests/testsuite/cargo_remove/mod.rs b/tests/testsuite/cargo_remove/mod.rs index 7b9190642ca..3510ece4b40 100644 --- a/tests/testsuite/cargo_remove/mod.rs +++ b/tests/testsuite/cargo_remove/mod.rs @@ -15,6 +15,7 @@ mod invalid_section; mod invalid_section_dep; mod invalid_target; mod invalid_target_dep; +mod last_dep; mod multiple_deps; mod multiple_dev; mod no_arg; @@ -24,6 +25,8 @@ mod optional_dep_feature_formatting; mod optional_feature; mod package; mod remove_basic; +mod script; +mod script_last; mod skip_gc_glob_profile; mod target; mod target_build; diff --git a/tests/testsuite/cargo_remove/script/in/cargo-remove-test-fixture.rs b/tests/testsuite/cargo_remove/script/in/cargo-remove-test-fixture.rs new file mode 100644 index 00000000000..99662ce110a --- /dev/null +++ b/tests/testsuite/cargo_remove/script/in/cargo-remove-test-fixture.rs @@ -0,0 +1,23 @@ +--- +edition = "2015" + +[build-dependencies] +semver = "0.1.0" + +[dependencies] +docopt = "0.6" +rustc-serialize = "0.4" +semver = "0.1" +toml = "0.1" +clippy = "0.4" + +[dev-dependencies] +regex = "0.1.1" +serde = "1.0.90" + +[features] +std = ["serde/std", "semver/std"] +--- + +fn main() { +} diff --git a/tests/testsuite/cargo_remove/script/mod.rs b/tests/testsuite/cargo_remove/script/mod.rs new file mode 100644 index 00000000000..03207eb2630 --- /dev/null +++ b/tests/testsuite/cargo_remove/script/mod.rs @@ -0,0 +1,40 @@ +use cargo_test_support::compare::assert_ui; +use cargo_test_support::current_dir; +use cargo_test_support::file; +use cargo_test_support::prelude::*; +use cargo_test_support::str; +use cargo_test_support::CargoCommandExt; +use cargo_test_support::Project; + +#[cargo_test] +fn case() { + cargo_test_support::registry::init(); + cargo_test_support::registry::Package::new("clippy", "0.4.0+my-package").publish(); + cargo_test_support::registry::Package::new("docopt", "0.6.2+my-package").publish(); + cargo_test_support::registry::Package::new("regex", "0.1.1+my-package").publish(); + cargo_test_support::registry::Package::new("rustc-serialize", "0.4.0+my-package").publish(); + cargo_test_support::registry::Package::new("toml", "0.1.1+my-package").publish(); + cargo_test_support::registry::Package::new("semver", "0.1.1") + .feature("std", &[]) + .publish(); + cargo_test_support::registry::Package::new("serde", "1.0.90") + .feature("std", &[]) + .publish(); + + let project = Project::from_template(current_dir!().join("in")); + let project_root = project.root(); + let cwd = &project_root; + + snapbox::cmd::Command::cargo_ui() + .masquerade_as_nightly_cargo(&["script"]) + .arg("-Zscript") + .arg("remove") + .arg_line("--manifest-path cargo-remove-test-fixture.rs docopt") + .current_dir(cwd) + .assert() + .success() + .stdout_eq(str![""]) + .stderr_eq(file!["stderr.term.svg"]); + + assert_ui().subset_matches(current_dir!().join("out"), &project_root); +} diff --git a/tests/testsuite/cargo_remove/script/out/cargo-remove-test-fixture.rs b/tests/testsuite/cargo_remove/script/out/cargo-remove-test-fixture.rs new file mode 100644 index 00000000000..16bdb60288c --- /dev/null +++ b/tests/testsuite/cargo_remove/script/out/cargo-remove-test-fixture.rs @@ -0,0 +1,22 @@ +--- +edition = "2015" + +[build-dependencies] +semver = "0.1.0" + +[dependencies] +rustc-serialize = "0.4" +semver = "0.1" +toml = "0.1" +clippy = "0.4" + +[dev-dependencies] +regex = "0.1.1" +serde = "1.0.90" + +[features] +std = ["serde/std", "semver/std"] +--- + +fn main() { +} diff --git a/tests/testsuite/cargo_remove/script/stderr.term.svg b/tests/testsuite/cargo_remove/script/stderr.term.svg new file mode 100644 index 00000000000..6e54530df8b --- /dev/null +++ b/tests/testsuite/cargo_remove/script/stderr.term.svg @@ -0,0 +1,32 @@ + + + + + + + warning: `package.edition` is unspecified, defaulting to `[..]` + + Removing docopt from dependencies + + warning: `package.edition` is unspecified, defaulting to `[..]` + + + + + + diff --git a/tests/testsuite/cargo_remove/script_last/in/cargo-remove-test-fixture.rs b/tests/testsuite/cargo_remove/script_last/in/cargo-remove-test-fixture.rs new file mode 100644 index 00000000000..67950478399 --- /dev/null +++ b/tests/testsuite/cargo_remove/script_last/in/cargo-remove-test-fixture.rs @@ -0,0 +1,7 @@ +--- +[dependencies] +docopt = "0.6" +--- + +fn main() { +} diff --git a/tests/testsuite/cargo_remove/script_last/mod.rs b/tests/testsuite/cargo_remove/script_last/mod.rs new file mode 100644 index 00000000000..1b7ec0403a5 --- /dev/null +++ b/tests/testsuite/cargo_remove/script_last/mod.rs @@ -0,0 +1,30 @@ +use cargo_test_support::compare::assert_ui; +use cargo_test_support::current_dir; +use cargo_test_support::file; +use cargo_test_support::prelude::*; +use cargo_test_support::str; +use cargo_test_support::CargoCommandExt; +use cargo_test_support::Project; + +#[cargo_test] +fn case() { + cargo_test_support::registry::init(); + cargo_test_support::registry::Package::new("docopt", "0.6.2+my-package").publish(); + + let project = Project::from_template(current_dir!().join("in")); + let project_root = project.root(); + let cwd = &project_root; + + snapbox::cmd::Command::cargo_ui() + .masquerade_as_nightly_cargo(&["script"]) + .arg("-Zscript") + .arg("remove") + .arg_line("--manifest-path cargo-remove-test-fixture.rs docopt") + .current_dir(cwd) + .assert() + .success() + .stdout_eq(str![""]) + .stderr_eq(file!["stderr.term.svg"]); + + assert_ui().subset_matches(current_dir!().join("out"), &project_root); +} diff --git a/tests/testsuite/cargo_remove/script_last/out/cargo-remove-test-fixture.rs b/tests/testsuite/cargo_remove/script_last/out/cargo-remove-test-fixture.rs new file mode 100644 index 00000000000..f299193f900 --- /dev/null +++ b/tests/testsuite/cargo_remove/script_last/out/cargo-remove-test-fixture.rs @@ -0,0 +1,6 @@ +--- + +--- + +fn main() { +} diff --git a/tests/testsuite/cargo_remove/script_last/stderr.term.svg b/tests/testsuite/cargo_remove/script_last/stderr.term.svg new file mode 100644 index 00000000000..6e54530df8b --- /dev/null +++ b/tests/testsuite/cargo_remove/script_last/stderr.term.svg @@ -0,0 +1,32 @@ + + + + + + + warning: `package.edition` is unspecified, defaulting to `[..]` + + Removing docopt from dependencies + + warning: `package.edition` is unspecified, defaulting to `[..]` + + + + + +