From e0477a38690817a4bfd92982da7b8ab99c183534 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 25 Nov 2024 10:14:00 -0600 Subject: [PATCH 1/9] test(remove): Show what happens with last dep in a table --- .../cargo_remove/last_dep/in/Cargo.toml | 13 +++++++++ .../cargo_remove/last_dep/in/src/lib.rs | 1 + tests/testsuite/cargo_remove/last_dep/mod.rs | 28 +++++++++++++++++++ .../cargo_remove/last_dep/out/Cargo.toml | 10 +++++++ .../cargo_remove/last_dep/stderr.term.svg | 27 ++++++++++++++++++ tests/testsuite/cargo_remove/mod.rs | 1 + 6 files changed, 80 insertions(+) create mode 100644 tests/testsuite/cargo_remove/last_dep/in/Cargo.toml create mode 100644 tests/testsuite/cargo_remove/last_dep/in/src/lib.rs create mode 100644 tests/testsuite/cargo_remove/last_dep/mod.rs create mode 100644 tests/testsuite/cargo_remove/last_dep/out/Cargo.toml create mode 100644 tests/testsuite/cargo_remove/last_dep/stderr.term.svg 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..92a62439da5 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; From 589d09917cbadd5e63655f38173d223615221fc1 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 22 Nov 2024 16:29:38 -0600 Subject: [PATCH 2/9] test(script): Verify manifest modification commands --- tests/testsuite/cargo_add/mod.rs | 3 ++ .../script_bare/in/cargo-test-fixture.rs | 1 + tests/testsuite/cargo_add/script_bare/mod.rs | 39 +++++++++++++++ .../script_bare/out/cargo-test-fixture.rs | 1 + .../cargo_add/script_bare/stderr.term.svg | 50 +++++++++++++++++++ .../in/cargo-test-fixture.rs | 7 +++ .../cargo_add/script_frontmatter/mod.rs | 39 +++++++++++++++ .../out/cargo-test-fixture.rs | 7 +++ .../script_frontmatter/stderr.term.svg | 47 +++++++++++++++++ .../script_shebang/in/cargo-test-fixture.rs | 3 ++ .../testsuite/cargo_add/script_shebang/mod.rs | 39 +++++++++++++++ .../script_shebang/out/cargo-test-fixture.rs | 3 ++ .../cargo_add/script_shebang/stderr.term.svg | 50 +++++++++++++++++++ tests/testsuite/cargo_remove/mod.rs | 2 + .../script/in/cargo-remove-test-fixture.rs | 23 +++++++++ tests/testsuite/cargo_remove/script/mod.rs | 40 +++++++++++++++ .../script/out/cargo-remove-test-fixture.rs | 23 +++++++++ .../cargo_remove/script/stderr.term.svg | 50 +++++++++++++++++++ .../in/cargo-remove-test-fixture.rs | 7 +++ .../testsuite/cargo_remove/script_last/mod.rs | 30 +++++++++++ .../out/cargo-remove-test-fixture.rs | 7 +++ .../cargo_remove/script_last/stderr.term.svg | 50 +++++++++++++++++++ 22 files changed, 521 insertions(+) create mode 100644 tests/testsuite/cargo_add/script_bare/in/cargo-test-fixture.rs create mode 100644 tests/testsuite/cargo_add/script_bare/mod.rs create mode 100644 tests/testsuite/cargo_add/script_bare/out/cargo-test-fixture.rs create mode 100644 tests/testsuite/cargo_add/script_bare/stderr.term.svg create mode 100644 tests/testsuite/cargo_add/script_frontmatter/in/cargo-test-fixture.rs create mode 100644 tests/testsuite/cargo_add/script_frontmatter/mod.rs create mode 100644 tests/testsuite/cargo_add/script_frontmatter/out/cargo-test-fixture.rs create mode 100644 tests/testsuite/cargo_add/script_frontmatter/stderr.term.svg create mode 100644 tests/testsuite/cargo_add/script_shebang/in/cargo-test-fixture.rs create mode 100644 tests/testsuite/cargo_add/script_shebang/mod.rs create mode 100644 tests/testsuite/cargo_add/script_shebang/out/cargo-test-fixture.rs create mode 100644 tests/testsuite/cargo_add/script_shebang/stderr.term.svg create mode 100644 tests/testsuite/cargo_remove/script/in/cargo-remove-test-fixture.rs create mode 100644 tests/testsuite/cargo_remove/script/mod.rs create mode 100644 tests/testsuite/cargo_remove/script/out/cargo-remove-test-fixture.rs create mode 100644 tests/testsuite/cargo_remove/script/stderr.term.svg create mode 100644 tests/testsuite/cargo_remove/script_last/in/cargo-remove-test-fixture.rs create mode 100644 tests/testsuite/cargo_remove/script_last/mod.rs create mode 100644 tests/testsuite/cargo_remove/script_last/out/cargo-remove-test-fixture.rs create mode 100644 tests/testsuite/cargo_remove/script_last/stderr.term.svg 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..3b0f34317ab --- /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() + .failure() + .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..f328e4d9d04 --- /dev/null +++ b/tests/testsuite/cargo_add/script_bare/out/cargo-test-fixture.rs @@ -0,0 +1 @@ +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..f0187d20a34 --- /dev/null +++ b/tests/testsuite/cargo_add/script_bare/stderr.term.svg @@ -0,0 +1,50 @@ + + + + + + + warning: `package.edition` is unspecified, defaulting to `[..]` + + error: Unable to parse Cargo.toml + + + + Caused by: + + Manifest not valid TOML + + + + Caused by: + + TOML parse error at line 1, column 4 + + | + + 1 | fn main() {} + + | ^ + + expected `.`, `=` + + + + + + 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..3b0f34317ab --- /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() + .failure() + .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..129939352ae --- /dev/null +++ b/tests/testsuite/cargo_add/script_frontmatter/out/cargo-test-fixture.rs @@ -0,0 +1,7 @@ +--- +[package] +edition = "2015" +--- + +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..2b06ad6ded0 --- /dev/null +++ b/tests/testsuite/cargo_add/script_frontmatter/stderr.term.svg @@ -0,0 +1,47 @@ + + + + + + + error: Unable to parse Cargo.toml + + + + Caused by: + + Manifest not valid TOML + + + + Caused by: + + TOML parse error at line 1, column 4 + + | + + 1 | --- + + | ^ + + expected `.`, `=` + + + + + + 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..3b0f34317ab --- /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() + .failure() + .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..627aa3d895f --- /dev/null +++ b/tests/testsuite/cargo_add/script_shebang/out/cargo-test-fixture.rs @@ -0,0 +1,3 @@ +#!/usr/bin/env cargo + +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..934cf3776bc --- /dev/null +++ b/tests/testsuite/cargo_add/script_shebang/stderr.term.svg @@ -0,0 +1,50 @@ + + + + + + + warning: `package.edition` is unspecified, defaulting to `[..]` + + error: Unable to parse Cargo.toml + + + + Caused by: + + Manifest not valid TOML + + + + Caused by: + + TOML parse error at line 3, column 4 + + | + + 3 | fn main() {} + + | ^ + + expected `.`, `=` + + + + + + diff --git a/tests/testsuite/cargo_remove/mod.rs b/tests/testsuite/cargo_remove/mod.rs index 92a62439da5..3510ece4b40 100644 --- a/tests/testsuite/cargo_remove/mod.rs +++ b/tests/testsuite/cargo_remove/mod.rs @@ -25,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..b5d533b7e41 --- /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() + .failure() + .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..99662ce110a --- /dev/null +++ b/tests/testsuite/cargo_remove/script/out/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/stderr.term.svg b/tests/testsuite/cargo_remove/script/stderr.term.svg new file mode 100644 index 00000000000..4165d0ac770 --- /dev/null +++ b/tests/testsuite/cargo_remove/script/stderr.term.svg @@ -0,0 +1,50 @@ + + + + + + + warning: `package.edition` is unspecified, defaulting to `[..]` + + error: Unable to parse Cargo.toml + + + + Caused by: + + Manifest not valid TOML + + + + Caused by: + + TOML parse error at line 1, column 4 + + | + + 1 | --- + + | ^ + + expected `.`, `=` + + + + + + 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..68da9cc2d78 --- /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() + .failure() + .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..67950478399 --- /dev/null +++ b/tests/testsuite/cargo_remove/script_last/out/cargo-remove-test-fixture.rs @@ -0,0 +1,7 @@ +--- +[dependencies] +docopt = "0.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..4165d0ac770 --- /dev/null +++ b/tests/testsuite/cargo_remove/script_last/stderr.term.svg @@ -0,0 +1,50 @@ + + + + + + + warning: `package.edition` is unspecified, defaulting to `[..]` + + error: Unable to parse Cargo.toml + + + + Caused by: + + Manifest not valid TOML + + + + Caused by: + + TOML parse error at line 1, column 4 + + | + + 1 | --- + + | ^ + + expected `.`, `=` + + + + + + From 6ebd1aec1709b7868c41f543b5beebaf8128798b Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 25 Nov 2024 10:36:57 -0600 Subject: [PATCH 3/9] refactor(remove): Clarify variable names --- src/bin/cargo/commands/remove.rs | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/bin/cargo/commands/remove.rs b/src/bin/cargo/commands/remove.rs index 833fd00c549..6bccac53d64 100644 --- a/src/bin/cargo/commands/remove.rs +++ b/src/bin/cargo/commands/remove.rs @@ -161,7 +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 = + let mut workspace_manifest: toml_edit::DocumentMut = cargo_util::paths::read(workspace.root_manifest())?.parse()?; let mut is_modified = true; @@ -177,8 +177,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 +190,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 +203,7 @@ 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 .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.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.get_mut("replace") { table.set_implicit(true); for (key, item) in table.iter_mut() { @@ -298,7 +300,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.to_string().as_bytes(), )?; } @@ -340,12 +342,12 @@ 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 = + let mut workspace_manifest: toml_edit::DocumentMut = cargo_util::paths::read(workspace.root_manifest())?.parse()?; 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.get_mut("patch") { patch_section_table.set_implicit(true); for (_, item) in patch_section_table.iter_mut() { @@ -383,7 +385,10 @@ fn gc_unused_patches(workspace: &Workspace<'_>, resolve: &Resolve) -> CargoResul } if modified { - cargo_util::paths::write(workspace.root_manifest(), manifest.to_string().as_bytes())?; + cargo_util::paths::write( + workspace.root_manifest(), + workspace_manifest.to_string().as_bytes(), + )?; } Ok(modified) From 0b994144793186bd3874bf95fa453fbb4a7fce1c Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 25 Nov 2024 10:40:59 -0600 Subject: [PATCH 4/9] refactor(remove): Consolidate how we mutate manifests This will make it easier to add cargo script support --- src/bin/cargo/commands/remove.rs | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src/bin/cargo/commands/remove.rs b/src/bin/cargo/commands/remove.rs index 6bccac53d64..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 workspace_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 @@ -204,6 +203,7 @@ 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)) = workspace_manifest + .data .get_mut("workspace") .and_then(|t| t.get_mut("dependencies")) { @@ -247,7 +247,7 @@ fn gc_workspace(workspace: &Workspace<'_>) -> CargoResult<()> { // - profile.dev.package.foo // - profile.release.package."foo:2.1.0" if let Some(toml_edit::Item::Table(profile_section_table)) = - workspace_manifest.get_mut("profile") + workspace_manifest.data.get_mut("profile") { profile_section_table.set_implicit(true); @@ -282,7 +282,7 @@ fn gc_workspace(workspace: &Workspace<'_>) -> CargoResult<()> { } // Clean up the replace section - if let Some(toml_edit::Item::Table(table)) = workspace_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() { @@ -298,10 +298,7 @@ fn gc_workspace(workspace: &Workspace<'_>) -> CargoResult<()> { } if is_modified { - cargo_util::paths::write_atomic( - workspace.root_manifest(), - workspace_manifest.to_string().as_bytes(), - )?; + workspace_manifest.write()?; } Ok(()) @@ -342,12 +339,13 @@ fn spec_has_match( /// Removes unused patches from the manifest fn gc_unused_patches(workspace: &Workspace<'_>, resolve: &Resolve) -> CargoResult { - let mut workspace_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)) = workspace_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() { @@ -385,10 +383,7 @@ fn gc_unused_patches(workspace: &Workspace<'_>, resolve: &Resolve) -> CargoResul } if modified { - cargo_util::paths::write( - workspace.root_manifest(), - workspace_manifest.to_string().as_bytes(), - )?; + workspace_manifest.write()?; } Ok(modified) From 7ec1867ba04e98aae69b90ec025a3ac34fc8a333 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 22 Nov 2024 16:34:00 -0600 Subject: [PATCH 5/9] refactor(toml): Give more specific name to ScriptSource --- src/cargo/util/toml/embedded.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cargo/util/toml/embedded.rs b/src/cargo/util/toml/embedded.rs index 155b54cb18c..2a6d659c4f6 100644 --- a/src/cargo/util/toml/embedded.rs +++ b/src/cargo/util/toml/embedded.rs @@ -189,15 +189,15 @@ fn sanitize_name(name: &str) -> String { } #[derive(Debug)] -struct Source<'s> { +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 { +fn split_source(input: &str) -> CargoResult> { + let mut source = ScriptSource { shebang: None, info: None, frontmatter: None, From e82a4beaed1f1b72f6ecba5548cafdb766128ffc Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 22 Nov 2024 16:36:22 -0600 Subject: [PATCH 6/9] refactor(toml): Move the parse fn onto ScriptSource --- src/cargo/util/toml/embedded.rs | 154 ++++++++++++++++---------------- 1 file changed, 78 insertions(+), 76 deletions(-) diff --git a/src/cargo/util/toml/embedded.rs b/src/cargo/util/toml/embedded.rs index 2a6d659c4f6..8f7237872c3 100644 --- a/src/cargo/util/toml/embedded.rs +++ b/src/cargo/util/toml/embedded.rs @@ -21,7 +21,7 @@ pub(super) fn expand_manifest( path: &std::path::Path, gctx: &GlobalContext, ) -> CargoResult { - let source = split_source(content)?; + let source = ScriptSource::parse(content)?; if let Some(frontmatter) = source.frontmatter { match source.info { Some("cargo") | None => {} @@ -196,87 +196,89 @@ struct ScriptSource<'s> { content: &'s str, } -fn split_source(input: &str) -> CargoResult> { - let mut source = ScriptSource { - shebang: None, - info: None, - frontmatter: None, - content: input, - }; +impl<'s> ScriptSource<'s> { + 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; - } - 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 (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" - ) + 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.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; + 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}`"); + Ok(source) } - source.content = content; - - Ok(source) } #[cfg(test)] @@ -291,7 +293,7 @@ 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}"), }; @@ -497,7 +499,7 @@ content: "\nfn main() {}" #[test] fn split_too_few_dashes() { assert_err( - split_source( + ScriptSource::parse( r#"#!/usr/bin/env cargo -- [dependencies] @@ -513,7 +515,7 @@ fn main() {} #[test] fn split_mismatched_dashes() { assert_err( - split_source( + ScriptSource::parse( r#"#!/usr/bin/env cargo --- [dependencies] @@ -529,7 +531,7 @@ fn main() {} #[test] fn split_missing_close() { assert_err( - split_source( + ScriptSource::parse( r#"#!/usr/bin/env cargo --- [dependencies] From a0ccb13cb1116685d3ad06931246b74e1a2e0cac Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 22 Nov 2024 16:38:36 -0600 Subject: [PATCH 7/9] refactor(toml): Switch to using accessors with ScriptSource --- src/cargo/util/toml/embedded.rs | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/cargo/util/toml/embedded.rs b/src/cargo/util/toml/embedded.rs index 8f7237872c3..fa603a1eed1 100644 --- a/src/cargo/util/toml/embedded.rs +++ b/src/cargo/util/toml/embedded.rs @@ -22,8 +22,8 @@ pub(super) fn expand_manifest( gctx: &GlobalContext, ) -> CargoResult { let source = ScriptSource::parse(content)?; - if let Some(frontmatter) = source.frontmatter { - match source.info { + 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)?; } @@ -279,6 +279,22 @@ impl<'s> ScriptSource<'s> { Ok(source) } + + fn shebang(&self) -> Option<&'s str> { + self.shebang + } + + fn info(&self) -> Option<&'s str> { + self.info + } + + fn frontmatter(&self) -> Option<&'s str> { + self.frontmatter + } + + fn content(&self) -> &'s str { + self.content + } } #[cfg(test)] @@ -299,10 +315,10 @@ mod test_expand { }; 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()); } From 89d1fafbfdece54e32606bdc9398d249e7dd29ba Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 22 Nov 2024 16:39:52 -0600 Subject: [PATCH 8/9] refactor(toml): Expose ScriptSource for reuse --- src/cargo/util/toml/embedded.rs | 12 ++++++------ src/cargo/util/toml/mod.rs | 2 ++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/cargo/util/toml/embedded.rs b/src/cargo/util/toml/embedded.rs index fa603a1eed1..126188588b2 100644 --- a/src/cargo/util/toml/embedded.rs +++ b/src/cargo/util/toml/embedded.rs @@ -189,7 +189,7 @@ fn sanitize_name(name: &str) -> String { } #[derive(Debug)] -struct ScriptSource<'s> { +pub struct ScriptSource<'s> { shebang: Option<&'s str>, info: Option<&'s str>, frontmatter: Option<&'s str>, @@ -197,7 +197,7 @@ struct ScriptSource<'s> { } impl<'s> ScriptSource<'s> { - fn parse(input: &'s str) -> CargoResult { + pub fn parse(input: &'s str) -> CargoResult { let mut source = Self { shebang: None, info: None, @@ -280,19 +280,19 @@ impl<'s> ScriptSource<'s> { Ok(source) } - fn shebang(&self) -> Option<&'s str> { + pub fn shebang(&self) -> Option<&'s str> { self.shebang } - fn info(&self) -> Option<&'s str> { + pub fn info(&self) -> Option<&'s str> { self.info } - fn frontmatter(&self) -> Option<&'s str> { + pub fn frontmatter(&self) -> Option<&'s str> { self.frontmatter } - fn content(&self) -> &'s str { + pub fn content(&self) -> &'s str { self.content } } diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index bd1fcf142c9..d59ba0ef4ea 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(); From 26844a33315110b913340cd6b86662200c8ed5ee Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 22 Nov 2024 20:56:54 -0600 Subject: [PATCH 9/9] feat(toml): Allow adding/removing from cargo scripts --- src/cargo/util/toml_mut/dependency.rs | 2 + src/cargo/util/toml_mut/manifest.rs | 94 ++++++++++++++++++- tests/testsuite/cargo_add/script_bare/mod.rs | 2 +- .../script_bare/out/cargo-test-fixture.rs | 5 + .../cargo_add/script_bare/stderr.term.svg | 26 ++--- .../cargo_add/script_frontmatter/mod.rs | 2 +- .../out/cargo-test-fixture.rs | 3 + .../script_frontmatter/stderr.term.svg | 28 ++---- .../testsuite/cargo_add/script_shebang/mod.rs | 2 +- .../script_shebang/out/cargo-test-fixture.rs | 4 + .../cargo_add/script_shebang/stderr.term.svg | 26 ++--- tests/testsuite/cargo_remove/script/mod.rs | 2 +- .../script/out/cargo-remove-test-fixture.rs | 1 - .../cargo_remove/script/stderr.term.svg | 28 +----- .../testsuite/cargo_remove/script_last/mod.rs | 2 +- .../out/cargo-remove-test-fixture.rs | 3 +- .../cargo_remove/script_last/stderr.term.svg | 28 +----- 17 files changed, 139 insertions(+), 119 deletions(-) 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/script_bare/mod.rs b/tests/testsuite/cargo_add/script_bare/mod.rs index 3b0f34317ab..ffa6db6c6f8 100644 --- a/tests/testsuite/cargo_add/script_bare/mod.rs +++ b/tests/testsuite/cargo_add/script_bare/mod.rs @@ -31,7 +31,7 @@ fn case() { .arg_line("--manifest-path cargo-test-fixture.rs my-package") .current_dir(cwd) .assert() - .failure() + .success() .stdout_eq(str![""]) .stderr_eq(file!["stderr.term.svg"]); 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 index f328e4d9d04..caf9ce19959 100644 --- a/tests/testsuite/cargo_add/script_bare/out/cargo-test-fixture.rs +++ b/tests/testsuite/cargo_add/script_bare/out/cargo-test-fixture.rs @@ -1 +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 index f0187d20a34..b5c72375b88 100644 --- a/tests/testsuite/cargo_add/script_bare/stderr.term.svg +++ b/tests/testsuite/cargo_add/script_bare/stderr.term.svg @@ -1,8 +1,8 @@ - +