Auto merge of #12069 - hi-rustin:rustin-patch-ws, r=epage

Automatically inherit workspace fields when running cargo new/init
This commit is contained in:
bors 2023-05-23 13:09:13 +00:00
commit 50d56ca616
62 changed files with 730 additions and 50 deletions

View file

@ -1,5 +1,6 @@
use crate::core::{Edition, Shell, Workspace};
use crate::util::errors::CargoResult;
use crate::util::important_paths::find_root_manifest_for_wd;
use crate::util::{existing_vcs_repo, FossilRepo, GitRepo, HgRepo, PijulRepo};
use crate::util::{restricted_names, Config};
use anyhow::{anyhow, Context as _};
@ -759,69 +760,72 @@ fn mk(config: &Config, opts: &MkOptions<'_>) -> CargoResult<()> {
init_vcs(path, vcs, config)?;
write_ignore_file(path, &ignore, vcs)?;
let mut cargotoml_path_specifier = String::new();
// Create `Cargo.toml` file with necessary `[lib]` and `[[bin]]` sections, if needed.
let mut manifest = toml_edit::Document::new();
manifest["package"] = toml_edit::Item::Table(toml_edit::Table::new());
manifest["package"]["name"] = toml_edit::value(name);
manifest["package"]["version"] = toml_edit::value("0.1.0");
let edition = match opts.edition {
Some(edition) => edition.to_string(),
None => Edition::LATEST_STABLE.to_string(),
};
manifest["package"]["edition"] = toml_edit::value(edition);
if let Some(registry) = opts.registry {
let mut array = toml_edit::Array::default();
array.push(registry);
manifest["package"]["publish"] = toml_edit::value(array);
}
let mut dep_table = toml_edit::Table::default();
dep_table.decor_mut().set_prefix("\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n");
manifest["dependencies"] = toml_edit::Item::Table(dep_table);
// Calculate what `[lib]` and `[[bin]]`s we need to append to `Cargo.toml`.
for i in &opts.source_files {
if i.bin {
if i.relative_path != "src/main.rs" {
cargotoml_path_specifier.push_str(&format!(
r#"
[[bin]]
name = "{}"
path = {}
"#,
i.target_name,
toml::Value::String(i.relative_path.clone())
));
let mut bin = toml_edit::Table::new();
bin["name"] = toml_edit::value(i.target_name.clone());
bin["path"] = toml_edit::value(i.relative_path.clone());
manifest["bin"]
.or_insert(toml_edit::Item::ArrayOfTables(
toml_edit::ArrayOfTables::new(),
))
.as_array_of_tables_mut()
.expect("bin is an array of tables")
.push(bin);
}
} else if i.relative_path != "src/lib.rs" {
cargotoml_path_specifier.push_str(&format!(
r#"
[lib]
name = "{}"
path = {}
"#,
i.target_name,
toml::Value::String(i.relative_path.clone())
));
let mut lib = toml_edit::Table::new();
lib["name"] = toml_edit::value(i.target_name.clone());
lib["path"] = toml_edit::value(i.relative_path.clone());
manifest["lib"] = toml_edit::Item::Table(lib);
}
}
// Create `Cargo.toml` file with necessary `[lib]` and `[[bin]]` sections, if needed.
let manifest_path = path.join("Cargo.toml");
if let Ok(root_manifest_path) = find_root_manifest_for_wd(&manifest_path) {
let root_manifest = paths::read(&root_manifest_path)?;
// Sometimes the root manifest is not a valid manifest, so we only try to parse it if it is.
// This should not block the creation of the new project. It is only a best effort to
// inherit the workspace package keys.
if let Ok(workspace_document) = root_manifest.parse::<toml_edit::Document>() {
if let Some(workspace_package_keys) = workspace_document
.get("workspace")
.and_then(|workspace| workspace.get("package"))
.and_then(|package| package.as_table())
{
update_manifest_with_inherited_workspace_package_keys(
opts,
&mut manifest,
workspace_package_keys,
)
}
}
}
paths::write(
&path.join("Cargo.toml"),
format!(
r#"[package]
name = "{}"
version = "0.1.0"
edition = {}
{}
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
{}"#,
name,
match opts.edition {
Some(edition) => toml::Value::String(edition.to_string()),
None => toml::Value::String(Edition::LATEST_STABLE.to_string()),
},
match opts.registry {
Some(registry) => format!(
"publish = {}\n",
toml::Value::Array(vec!(toml::Value::String(registry.to_string())))
),
None => "".to_string(),
},
cargotoml_path_specifier
)
.as_bytes(),
)?;
paths::write(&manifest_path, manifest.to_string())?;
// Create all specified source files (with respective parent directories) if they don't exist.
for i in &opts.source_files {
let path_of_source_file = path.join(i.relative_path.clone());
@ -878,3 +882,40 @@ mod tests {
Ok(())
}
// Update the manifest with the inherited workspace package keys.
// If the option is not set, the key is removed from the manifest.
// If the option is set, keep the value from the manifest.
fn update_manifest_with_inherited_workspace_package_keys(
opts: &MkOptions<'_>,
manifest: &mut toml_edit::Document,
workspace_package_keys: &toml_edit::Table,
) {
if workspace_package_keys.is_empty() {
return;
}
let try_remove_and_inherit_package_key = |key: &str, manifest: &mut toml_edit::Document| {
let package = manifest["package"]
.as_table_mut()
.expect("package is a table");
package.remove(key);
let mut table = toml_edit::Table::new();
table.set_dotted(true);
table["workspace"] = toml_edit::value(true);
package.insert(key, toml_edit::Item::Table(table));
};
// Inherit keys from the workspace.
// Only keep the value from the manifest if the option is set.
for (key, _) in workspace_package_keys {
if key == "edition" && opts.edition.is_some() {
continue;
}
if key == "publish" && opts.registry.is_some() {
continue;
}
try_remove_and_inherit_package_key(key, manifest);
}
}

View file

@ -0,0 +1,21 @@
[workspace]
members = [
"crates/*",
]
[workspace.package]
authors = ["Rustaceans"]
description = "foo"
edition = "2018"
homepage = "foo"
keywords = ["foo", "bar"]
readme = "README.md"
rust-version = "1.67.0"
categories = ["algorithms"]
documentation = "foo"
exclude = ["foo"]
include = ["foo"]
license = "MIT OR Apache-2.0"
publish = false
repository = "foo"
version = "1.2.3"

View file

@ -0,0 +1,14 @@
pub fn add(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}

View file

@ -0,0 +1 @@
../inherit_workspace_package_table.in

View file

@ -0,0 +1,22 @@
use cargo_test_support::compare::assert_ui;
use cargo_test_support::curr_dir;
use cargo_test_support::CargoCommand;
use cargo_test_support::Project;
#[cargo_test]
fn case() {
let project = Project::from_template(curr_dir!().join("in"));
let project_root = project.root();
let cwd = &project_root;
snapbox::cmd::Command::cargo_ui()
.arg("new")
.args(["crates/foo"])
.current_dir(cwd)
.assert()
.success()
.stdout_matches_path(curr_dir!().join("stdout.log"))
.stderr_matches_path(curr_dir!().join("stderr.log"));
assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
}

View file

@ -0,0 +1,21 @@
[workspace]
members = [
"crates/*",
]
[workspace.package]
authors = ["Rustaceans"]
description = "foo"
edition = "2018"
homepage = "foo"
keywords = ["foo", "bar"]
readme = "README.md"
rust-version = "1.67.0"
categories = ["algorithms"]
documentation = "foo"
exclude = ["foo"]
include = ["foo"]
license = "MIT OR Apache-2.0"
publish = false
repository = "foo"
version = "1.2.3"

View file

@ -0,0 +1,21 @@
[package]
name = "foo"
authors.workspace = true
description.workspace = true
edition.workspace = true
homepage.workspace = true
keywords.workspace = true
readme.workspace = true
rust-version.workspace = true
categories.workspace = true
documentation.workspace = true
exclude.workspace = true
include.workspace = true
license.workspace = true
publish.workspace = true
repository.workspace = true
version.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View file

@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}

View file

@ -0,0 +1,14 @@
pub fn add(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}

View file

@ -0,0 +1 @@
Created binary (application) `crates/foo` package

View file

@ -0,0 +1 @@
../inherit_workspace_package_table.in

View file

@ -0,0 +1,22 @@
use cargo_test_support::compare::assert_ui;
use cargo_test_support::curr_dir;
use cargo_test_support::CargoCommand;
use cargo_test_support::Project;
#[cargo_test]
fn case() {
let project = Project::from_template(curr_dir!().join("in"));
let project_root = project.root();
let cwd = &project_root;
snapbox::cmd::Command::cargo_ui()
.arg("new")
.args(["crates/foo", "--edition", "2021"])
.current_dir(cwd)
.assert()
.success()
.stdout_matches_path(curr_dir!().join("stdout.log"))
.stderr_matches_path(curr_dir!().join("stderr.log"));
assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
}

View file

@ -0,0 +1,21 @@
[workspace]
members = [
"crates/*",
]
[workspace.package]
authors = ["Rustaceans"]
description = "foo"
edition = "2018"
homepage = "foo"
keywords = ["foo", "bar"]
readme = "README.md"
rust-version = "1.67.0"
categories = ["algorithms"]
documentation = "foo"
exclude = ["foo"]
include = ["foo"]
license = "MIT OR Apache-2.0"
publish = false
repository = "foo"
version = "1.2.3"

View file

@ -0,0 +1,21 @@
[package]
name = "foo"
edition = "2021"
authors.workspace = true
description.workspace = true
homepage.workspace = true
keywords.workspace = true
readme.workspace = true
rust-version.workspace = true
categories.workspace = true
documentation.workspace = true
exclude.workspace = true
include.workspace = true
license.workspace = true
publish.workspace = true
repository.workspace = true
version.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View file

@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}

View file

@ -0,0 +1,14 @@
pub fn add(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}

View file

@ -0,0 +1 @@
Created binary (application) `crates/foo` package

View file

@ -0,0 +1 @@
../inherit_workspace_package_table.in

View file

@ -0,0 +1,22 @@
use cargo_test_support::compare::assert_ui;
use cargo_test_support::curr_dir;
use cargo_test_support::CargoCommand;
use cargo_test_support::Project;
#[cargo_test]
fn case() {
let project = Project::from_template(curr_dir!().join("in"));
let project_root = project.root();
let cwd = &project_root;
snapbox::cmd::Command::cargo_ui()
.arg("new")
.args(["crates/foo", "--registry", "foo"])
.current_dir(cwd)
.assert()
.success()
.stdout_matches_path(curr_dir!().join("stdout.log"))
.stderr_matches_path(curr_dir!().join("stderr.log"));
assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
}

View file

@ -0,0 +1,21 @@
[workspace]
members = [
"crates/*",
]
[workspace.package]
authors = ["Rustaceans"]
description = "foo"
edition = "2018"
homepage = "foo"
keywords = ["foo", "bar"]
readme = "README.md"
rust-version = "1.67.0"
categories = ["algorithms"]
documentation = "foo"
exclude = ["foo"]
include = ["foo"]
license = "MIT OR Apache-2.0"
publish = false
repository = "foo"
version = "1.2.3"

View file

@ -0,0 +1,21 @@
[package]
name = "foo"
publish = ["foo"]
authors.workspace = true
description.workspace = true
edition.workspace = true
homepage.workspace = true
keywords.workspace = true
readme.workspace = true
rust-version.workspace = true
categories.workspace = true
documentation.workspace = true
exclude.workspace = true
include.workspace = true
license.workspace = true
repository.workspace = true
version.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View file

@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}

View file

@ -0,0 +1,14 @@
pub fn add(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}

View file

@ -0,0 +1 @@
Created binary (application) `crates/foo` package

View file

@ -0,0 +1,20 @@
[workspace]
members = [
"crates/*",
]
[workspace.package]
authors = ["Rustaceans"]
description = "foo"
edition = "2018"
homepage = "foo"
keywords = ["foo", "bar"]
readme = "README.md"
rust-version = "1.67.0"
categories = ["algorithms"]
documentation = "foo"
exclude = ["foo"]
include = ["foo"]
license = "MIT OR Apache-2.0"
publish = false
repository = "foo"

View file

@ -0,0 +1,14 @@
pub fn add(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}

View file

@ -0,0 +1,22 @@
use cargo_test_support::compare::assert_ui;
use cargo_test_support::curr_dir;
use cargo_test_support::CargoCommand;
use cargo_test_support::Project;
#[cargo_test]
fn case() {
let project = Project::from_template(curr_dir!().join("in"));
let project_root = project.root();
let cwd = &project_root;
snapbox::cmd::Command::cargo_ui()
.arg("new")
.args(["crates/foo"])
.current_dir(cwd)
.assert()
.success()
.stdout_matches_path(curr_dir!().join("stdout.log"))
.stderr_matches_path(curr_dir!().join("stderr.log"));
assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
}

View file

@ -0,0 +1,20 @@
[workspace]
members = [
"crates/*",
]
[workspace.package]
authors = ["Rustaceans"]
description = "foo"
edition = "2018"
homepage = "foo"
keywords = ["foo", "bar"]
readme = "README.md"
rust-version = "1.67.0"
categories = ["algorithms"]
documentation = "foo"
exclude = ["foo"]
include = ["foo"]
license = "MIT OR Apache-2.0"
publish = false
repository = "foo"

View file

@ -0,0 +1,21 @@
[package]
name = "foo"
version = "0.1.0"
authors.workspace = true
description.workspace = true
edition.workspace = true
homepage.workspace = true
keywords.workspace = true
readme.workspace = true
rust-version.workspace = true
categories.workspace = true
documentation.workspace = true
exclude.workspace = true
include.workspace = true
license.workspace = true
publish.workspace = true
repository.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View file

@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}

View file

@ -0,0 +1,14 @@
pub fn add(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}

View file

@ -0,0 +1 @@
Created binary (application) `crates/foo` package

View file

@ -0,0 +1,5 @@
mod inherit_workspace_package_table;
mod inherit_workspace_package_table_with_edition;
mod inherit_workspace_package_table_with_registry;
mod inherit_workspace_package_table_without_version;
mod not_inherit_workspace_package_table_if_not_memebers;

View file

@ -0,0 +1,15 @@
[workspace.package]
authors = ["Rustaceans"]
description = "foo"
edition = "2018"
homepage = "foo"
keywords = ["foo", "bar"]
readme = "README.md"
rust-version = "1.67.0"
categories = ["algorithms"]
documentation = "foo"
exclude = ["foo"]
include = ["foo"]
license = "MIT OR Apache-2.0"
publish = false
repository = "foo"

View file

@ -0,0 +1,14 @@
pub fn add(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}

View file

@ -0,0 +1,22 @@
use cargo_test_support::compare::assert_ui;
use cargo_test_support::curr_dir;
use cargo_test_support::CargoCommand;
use cargo_test_support::Project;
#[cargo_test]
fn case() {
let project = Project::from_template(curr_dir!().join("in"));
let project_root = project.root();
let cwd = &project_root;
snapbox::cmd::Command::cargo_ui()
.arg("new")
.args(["foo"])
.current_dir(cwd)
.assert()
.success()
.stdout_matches_path(curr_dir!().join("stdout.log"))
.stderr_matches_path(curr_dir!().join("stderr.log"));
assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
}

View file

@ -0,0 +1,15 @@
[workspace.package]
authors = ["Rustaceans"]
description = "foo"
edition = "2018"
homepage = "foo"
keywords = ["foo", "bar"]
readme = "README.md"
rust-version = "1.67.0"
categories = ["algorithms"]
documentation = "foo"
exclude = ["foo"]
include = ["foo"]
license = "MIT OR Apache-2.0"
publish = false
repository = "foo"

View file

@ -0,0 +1,21 @@
[package]
name = "foo"
version = "0.1.0"
authors.workspace = true
description.workspace = true
edition.workspace = true
homepage.workspace = true
keywords.workspace = true
readme.workspace = true
rust-version.workspace = true
categories.workspace = true
documentation.workspace = true
exclude.workspace = true
include.workspace = true
license.workspace = true
publish.workspace = true
repository.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View file

@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}

View file

@ -0,0 +1,14 @@
pub fn add(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}

View file

@ -0,0 +1,9 @@
warning: compiling this new package may not work due to invalid workspace configuration
current package believes it's in a workspace when it's not:
current: [ROOT]/case/foo/Cargo.toml
workspace: [ROOT]/case/Cargo.toml
this may be fixable by adding `foo` to the `workspace.members` array of the manifest located at: [ROOT]/case/Cargo.toml
Alternatively, to keep it out of the workspace, add the package to the `workspace.exclude` array, or add an empty `[workspace]` table to the package's manifest.
Created binary (application) `foo` package

View file

@ -0,0 +1,21 @@
[workspace]
members = [
"crates/*",
]
[workspace.package]
authors = ["Rustaceans"]
description = "foo"
edition = "2018"
homepage = "foo"
keywords = ["foo", "bar"]
readme = "README.md"
rust-version = "1.67.0"
categories = ["algorithms"]
documentation = "foo"
exclude = ["foo"]
include = ["foo"]
license = "MIT OR Apache-2.0"
publish = false
repository = "foo"
version = "1.2.3"

View file

@ -0,0 +1,3 @@
fn main() {
println!("Check that our file is not overwritten")
}

View file

@ -0,0 +1,14 @@
pub fn add(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}

View file

@ -0,0 +1,22 @@
use cargo_test_support::compare::assert_ui;
use cargo_test_support::curr_dir;
use cargo_test_support::CargoCommand;
use cargo_test_support::Project;
#[cargo_test]
fn case() {
let project = Project::from_template(curr_dir!().join("in"));
let project_root = project.root();
let cwd = &project_root;
snapbox::cmd::Command::cargo_ui()
.arg("init")
.args(["crates/foo"])
.current_dir(cwd)
.assert()
.success()
.stdout_matches_path(curr_dir!().join("stdout.log"))
.stderr_matches_path(curr_dir!().join("stderr.log"));
assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
}

View file

@ -0,0 +1,21 @@
[workspace]
members = [
"crates/*",
]
[workspace.package]
authors = ["Rustaceans"]
description = "foo"
edition = "2018"
homepage = "foo"
keywords = ["foo", "bar"]
readme = "README.md"
rust-version = "1.67.0"
categories = ["algorithms"]
documentation = "foo"
exclude = ["foo"]
include = ["foo"]
license = "MIT OR Apache-2.0"
publish = false
repository = "foo"
version = "1.2.3"

View file

@ -0,0 +1,21 @@
[package]
name = "foo"
authors.workspace = true
description.workspace = true
edition.workspace = true
homepage.workspace = true
keywords.workspace = true
readme.workspace = true
rust-version.workspace = true
categories.workspace = true
documentation.workspace = true
exclude.workspace = true
include.workspace = true
license.workspace = true
publish.workspace = true
repository.workspace = true
version.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View file

@ -0,0 +1,3 @@
fn main() {
println!("Check that our file is not overwritten")
}

View file

@ -0,0 +1,14 @@
pub fn add(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}

View file

@ -0,0 +1 @@
Created binary (application) package

View file

@ -21,6 +21,7 @@ mod git_ignore_exists_no_conflicting_entries;
mod ignores_failure_to_format_source;
mod inferred_bin_with_git;
mod inferred_lib_with_git;
mod inherit_workspace_package_table;
mod invalid_dir_name;
mod lib_already_exists_nosrc;
mod lib_already_exists_src;

View file

@ -24,6 +24,7 @@ mod cargo_command;
mod cargo_config;
mod cargo_env_config;
mod cargo_features;
mod cargo_new;
mod cargo_remove;
mod cargo_targets;
mod cfg;