Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Automatically inherit workspace fields when running cargo new/init #12069

Merged
merged 4 commits into from
May 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 91 additions & 50 deletions src/cargo/ops/cargo_new.rs
Original file line number Diff line number Diff line change
@@ -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 _};
Expand Down Expand Up @@ -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();
Rustin170506 marked this conversation as resolved.
Show resolved Hide resolved
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(
epage marked this conversation as resolved.
Show resolved Hide resolved
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);
Rustin170506 marked this conversation as resolved.
Show resolved Hide resolved
}
}

// Create `Cargo.toml` file with necessary `[lib]` and `[[bin]]` sections, if needed.
let manifest_path = path.join("Cargo.toml");
Rustin170506 marked this conversation as resolved.
Show resolved Hide resolved
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());

Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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"
Empty file.
Original file line number Diff line number Diff line change
@@ -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);
}
}
22 changes: 22 additions & 0 deletions tests/testsuite/cargo_new/inherit_workspace_package_table/mod.rs
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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"
Original file line number Diff line number Diff line change
@@ -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]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Created binary (application) `crates/foo` package
Empty file.
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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"
Original file line number Diff line number Diff line change
@@ -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]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Created binary (application) `crates/foo` package
Loading