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

Allow passing multiple crate names to cargo new #9867

Closed
wants to merge 9 commits into from
Closed
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
21 changes: 9 additions & 12 deletions src/bin/cargo/commands/new.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub fn cli() -> App {
subcommand("new")
.about("Create a new cargo package at <path>")
.arg(opt("quiet", "No output printed to stdout").short("q"))
.arg(Arg::with_name("path").required(true))
.arg(Arg::with_name("path").required(true).use_delimiter(true))
.arg(opt("registry", "Registry to use").value_name("REGISTRY"))
.arg_new_opts()
.after_help("Run `cargo help new` for more detailed information.\n")
Expand All @@ -15,16 +15,13 @@ pub fn cli() -> App {
pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
let opts = args.new_options(config)?;

ops::new(&opts, config)?;
let path = args.value_of("path").unwrap();
let package_name = if let Some(name) = args.value_of("name") {
name
} else {
path
};
config.shell().status(
"Created",
format!("{} `{}` package", opts.kind, package_name),
)?;
// obtain all packages on the path.
let paths = args
.values_of("path")
.unwrap_or_default()
.collect::<Vec<_>>();

ops::new(&opts, paths, config)?;

Ok(())
}
92 changes: 79 additions & 13 deletions src/cargo/ops/cargo_new.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::core::{Edition, Shell, Workspace};
use crate::util::errors::CargoResult;
use crate::util::{existing_vcs_repo, FossilRepo, GitRepo, HgRepo, PijulRepo};
use crate::util::{restricted_names, Config};
use anyhow::Context as _;
use anyhow::{bail, Context as _};
use cargo_util::paths;
use serde::de;
use serde::Deserialize;
Expand Down Expand Up @@ -412,26 +412,32 @@ fn calculate_new_project_kind(
requested_kind
}

pub fn new(opts: &NewOptions, config: &Config) -> CargoResult<()> {
let path = &opts.path;
if path.exists() {
fn new_one(opts: &NewOptions, name: Option<&str>, config: &Config) -> CargoResult<()> {
// Current working path
let work_path = config.cwd();
let package_name = name.map(str::to_string).unwrap();
let absolute_path = work_path.join(Path::new(&package_name));

if work_path.join(&package_name).exists() {
anyhow::bail!(
"destination `{}` already exists\n\n\
Use `cargo init` to initialize the directory",
path.display()
work_path.join(package_name).display()
)
}

let is_bin = opts.kind.is_bin();

let name = get_name(path, opts)?;
check_name(name, opts.name.is_none(), is_bin, &mut config.shell())?;
let file_name = get_name(&opts.path, opts)?;
check_name(file_name, opts.name.is_none(), is_bin, &mut config.shell())?;

let mkopts = MkOptions {
version_control: opts.version_control,
path,
name,
source_files: vec![plan_new_source_file(opts.kind.is_bin(), name.to_string())],
path: absolute_path.as_path(),
name: file_name,
source_files: vec![plan_new_source_file(
opts.kind.is_bin(),
file_name.to_string(),
)],
bin: is_bin,
edition: opts.edition.as_deref(),
registry: opts.registry.as_deref(),
Expand All @@ -440,13 +446,73 @@ pub fn new(opts: &NewOptions, config: &Config) -> CargoResult<()> {
mk(config, &mkopts).with_context(|| {
format!(
"Failed to create package `{}` at `{}`",
name,
path.display()
name.unwrap(),
absolute_path.as_path().display()
)
})?;
Ok(())
}

pub fn new(opts: &NewOptions, paths: Vec<&str>, config: &Config) -> CargoResult<()> {
let (success, error) = if paths.len() <= 1 {
new_one(opts, paths.clone().into_iter().next(), config)?;
(true, false)
} else {
let mut succeeded = vec![];
let mut failed = vec![];

for path in paths.clone() {
match new_one(opts, Some(path), config) {
Ok(()) => succeeded.push(path),
Err(e) => {
crate::display_error(&e, &mut config.shell());
failed.push(path)
}
}
}

let mut summary = vec![];
if !succeeded.is_empty() {
summary.push(format!(
"Successfully crated {} '{}' !",
opts.kind,
succeeded.join(", ")
));
}
if !failed.is_empty() {
summary.push(format!(
"Failed to crated {} '{}' !",
opts.kind,
failed.join(", ")
));
}

if !succeeded.is_empty() || !failed.is_empty() {
config.shell().status("Summary", summary.join(" "))?;
}
(!succeeded.is_empty(), !failed.is_empty())
};

if success && paths.len() == 1 {
let package_name = if opts.name.is_some() {
opts.name.as_ref().unwrap()
} else {
paths.clone().into_iter().next().unwrap()
};

config.shell().status(
"Created",
format!("{} `{}` package", opts.kind, package_name),
)?;
}

if error {
bail!("some packages failed to crated");
}

Ok(())
}

pub fn init(opts: &NewOptions, config: &Config) -> CargoResult<NewProjectKind> {
// This is here just as a random location to exercise the internal error handling.
if std::env::var_os("__CARGO_TEST_INTERNAL_ERROR").is_some() {
Expand Down
204 changes: 204 additions & 0 deletions tests/testsuite/new.rs
Original file line number Diff line number Diff line change
Expand Up @@ -494,3 +494,207 @@ fn git_default_branch() {
let head = repo.find_reference("HEAD").unwrap();
assert_eq!(head.symbolic_target().unwrap(), "refs/heads/hello");
}

#[cargo_test]
fn mulit_crate() {
// Check for creating multiple projects at the same time
cargo_process("new a,b,c")
.with_stderr("[SUMMARY] Successfully crated binary (application) 'a, b, c' !")
.run();

assert!(paths::root().join("a").is_dir());
assert!(paths::root().join("a/Cargo.toml").is_file());
assert!(paths::root().join("a/src/main.rs").is_file());

assert!(paths::root().join("b").is_dir());
assert!(paths::root().join("b/Cargo.toml").is_file());
assert!(paths::root().join("b/src/main.rs").is_file());

assert!(paths::root().join("c").is_dir());
assert!(paths::root().join("c/Cargo.toml").is_file());
assert!(paths::root().join("c/src/main.rs").is_file());
}

#[cargo_test]
fn mulit_crate_vcs() {
cargo_process("new a,b,c --vcs git").run();

assert!(paths::root().join("a/.gitignore").exists());
assert!(paths::root().join("a").is_dir());

assert!(paths::root().join("b/.gitignore").exists());
assert!(paths::root().join("b").is_dir());

assert!(paths::root().join("c/.gitignore").exists());
assert!(paths::root().join("c").is_dir());
}

#[cargo_test]
fn mulit_crate_edition() {
cargo_process("new a,b,c --edition 2018")
.with_stderr("[SUMMARY] Successfully crated binary (application) 'a, b, c' !")
.run();

let contens = fs::read_to_string(paths::root().join("a/Cargo.toml")).unwrap();
assert!(contens.contains("edition = \"2018\""));

let contens = fs::read_to_string(paths::root().join("b/Cargo.toml")).unwrap();
assert!(contens.contains("edition = \"2018\""));

let contens = fs::read_to_string(paths::root().join("c/Cargo.toml")).unwrap();
assert!(contens.contains("edition = \"2018\""));
}

#[cargo_test]
fn mulit_crate_name() {
cargo_process("new a,b,c --name bar")
.with_stderr("[SUMMARY] Successfully crated binary (application) 'a, b, c' !")
.run();

assert!(paths::root().join("a").is_dir());
assert!(paths::root().join("a/Cargo.toml").is_file());
assert!(paths::root().join("a/src/main.rs").exists());
let contens = fs::read_to_string(paths::root().join("a/Cargo.toml")).unwrap();
assert!(contens.contains("name = \"bar\""));

assert!(paths::root().join("b").is_dir());
assert!(paths::root().join("b/Cargo.toml").is_file());
assert!(paths::root().join("b/src/main.rs").exists());
let contens = fs::read_to_string(paths::root().join("b/Cargo.toml")).unwrap();
assert!(contens.contains("name = \"bar\""));

assert!(paths::root().join("c").is_dir());
assert!(paths::root().join("c/Cargo.toml").is_file());
assert!(paths::root().join("c/src/main.rs").exists());
let contens = fs::read_to_string(paths::root().join("c/Cargo.toml")).unwrap();
assert!(contens.contains("name = \"bar\""));
}

#[cargo_test]
fn mulit_lib() {
// Check for creating multiple lib at the same time
cargo_process("new --lib a,b,c --edition 2018").run();

assert!(paths::root().is_dir());
assert!(paths::root().join("a/Cargo.toml").is_file());
assert!(paths::root().join("a/src/lib.rs").is_file());
assert!(paths::root().join("a/.git").is_dir());
assert!(paths::root().join("a/.gitignore").is_file());

let fp = paths::root().join("a/.gitignore");
let contents = fs::read_to_string(&fp).unwrap();
assert_eq!(contents, "/target\nCargo.lock\n",);

cargo_process("build").cwd(&paths::root().join("a")).run();

assert!(paths::root().is_dir());
assert!(paths::root().join("b/Cargo.toml").is_file());
assert!(paths::root().join("b/src/lib.rs").is_file());
assert!(paths::root().join("b/.git").is_dir());
assert!(paths::root().join("b/.gitignore").is_file());

let fp = paths::root().join("b/.gitignore");
let contents = fs::read_to_string(&fp).unwrap();
assert_eq!(contents, "/target\nCargo.lock\n",);

cargo_process("build").cwd(&paths::root().join("b")).run();

assert!(paths::root().is_dir());
assert!(paths::root().join("c/Cargo.toml").is_file());
assert!(paths::root().join("c/src/lib.rs").is_file());
assert!(paths::root().join("c/.git").is_dir());
assert!(paths::root().join("c/.gitignore").is_file());

let fp = paths::root().join("c/.gitignore");
let contents = fs::read_to_string(&fp).unwrap();
assert_eq!(contents, "/target\nCargo.lock\n",);

cargo_process("build").cwd(&paths::root().join("c")).run();
}

#[cargo_test]
fn mulit_lib_vcs() {
cargo_process("new --lib a,b,c --vcs git")
.with_stderr("[SUMMARY] Successfully crated library 'a, b, c' !")
.run();

assert!(paths::root().join("a").is_dir());
assert!(paths::root().join("b").is_dir());
assert!(paths::root().join("c").is_dir());
}

#[cargo_test]
fn mulit_lib_name() {
cargo_process("new --lib a,b,c --name bar")
.with_stderr("[SUMMARY] Successfully crated library 'a, b, c' !")
.run();

assert!(paths::root().join("a").is_dir());
assert!(paths::root().join("a").join("src/lib.rs").exists());
let a_contens = fs::read_to_string(paths::root().join("a/Cargo.toml")).unwrap();
assert!(a_contens.contains("name = \"bar\""));

assert!(paths::root().join("b").is_dir());
assert!(paths::root().join("b").join("src/lib.rs").exists());
let b_contens = fs::read_to_string(paths::root().join("b/Cargo.toml")).unwrap();
assert!(b_contens.contains("name = \"bar\""));

assert!(paths::root().join("c").is_dir());
assert!(paths::root().join("c").join("src/lib.rs").exists());
let c_contens = fs::read_to_string(paths::root().join("c/Cargo.toml")).unwrap();
assert!(c_contens.contains("name = \"bar\""));
}

#[cargo_test]
fn mulit_lib_registry() {
cargo_process("new --lib a,b,c --registry bar")
.with_stderr("[SUMMARY] Successfully crated library 'a, b, c' !")
.run();

assert!(paths::root().join("a").is_dir());
assert!(paths::root().join("a").join("src/lib.rs").exists());
let a_contens = fs::read_to_string(paths::root().join("a/Cargo.toml")).unwrap();
assert!(a_contens.contains("publish = [\"bar\"]"));

assert!(paths::root().join("b").is_dir());
assert!(paths::root().join("b").join("src/lib.rs").exists());
let b_contens = fs::read_to_string(paths::root().join("b/Cargo.toml")).unwrap();
assert!(b_contens.contains("publish = [\"bar\"]"));

assert!(paths::root().join("c").is_dir());
assert!(paths::root().join("c").join("src/lib.rs").exists());
let c_contens = fs::read_to_string(paths::root().join("c/Cargo.toml")).unwrap();
assert!(c_contens.contains("publish = [\"bar\"]"));
}

#[cargo_test]
fn mulit_crate_or_lib_failed() {
// test of cargo new specifying multiple name values.
cargo_process("new a,b,c --name foo bar")
.with_status(1)
.with_stderr_contains(
"error: Found argument 'bar' which wasn't expected, or isn't valid in this context",
)
.run();

// test of cargo new --lib specifying multiple name values.
cargo_process("new --lib a,b,c --name foo bar")
.with_status(1)
.with_stderr_contains(
"error: Found argument 'bar' which wasn't expected, or isn't valid in this context",
)
.run();

// test of cargo new an existing crate.
let dst = paths::root().join("a");
fs::create_dir(&dst).unwrap();
cargo_process("new a,b,c")
.with_status(101)
.with_stderr(
"[ERROR] destination `[CWD]/a` already exists\n\n\
Use `cargo init` to initialize the directory
[SUMMARY] Successfully crated binary (application) 'b, c' ! Failed to crated binary (application) 'a' !
[ERROR] some packages failed to crated",
)
.run();
}