Skip to content

Commit 2544bd3

Browse files
committed
Auto merge of #9522 - Aelnor:respect_user_choice_of_binlib, r=ehuss
respect user choice of lib/bin over heuristics This one fixes #9333
2 parents 2b3af39 + 4ec3bc0 commit 2544bd3

File tree

3 files changed

+129
-18
lines changed

3 files changed

+129
-18
lines changed

src/bin/cargo/commands/init.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ pub fn cli() -> App {
1414

1515
pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
1616
let opts = args.new_options(config)?;
17-
ops::init(&opts, config)?;
17+
let project_kind = ops::init(&opts, config)?;
1818
config
1919
.shell()
20-
.status("Created", format!("{} package", opts.kind))?;
20+
.status("Created", format!("{} package", project_kind))?;
2121
Ok(())
2222
}

src/cargo/ops/cargo_new.rs

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ impl<'de> de::Deserialize<'de> for VersionControl {
5151
pub struct NewOptions {
5252
pub version_control: Option<VersionControl>,
5353
pub kind: NewProjectKind,
54+
pub auto_detect_kind: bool,
5455
/// Absolute path to the directory for the new package
5556
pub path: PathBuf,
5657
pub name: Option<String>,
@@ -106,16 +107,18 @@ impl NewOptions {
106107
edition: Option<String>,
107108
registry: Option<String>,
108109
) -> CargoResult<NewOptions> {
110+
let auto_detect_kind = !bin && !lib;
111+
109112
let kind = match (bin, lib) {
110113
(true, true) => anyhow::bail!("can't specify both lib and binary outputs"),
111114
(false, true) => NewProjectKind::Lib,
112-
// default to bin
113115
(_, false) => NewProjectKind::Bin,
114116
};
115117

116118
let opts = NewOptions {
117119
version_control,
118120
kind,
121+
auto_detect_kind,
119122
path,
120123
name,
121124
edition,
@@ -389,6 +392,26 @@ fn plan_new_source_file(bin: bool, package_name: String) -> SourceFileInformatio
389392
}
390393
}
391394

395+
fn calculate_new_project_kind(
396+
requested_kind: NewProjectKind,
397+
auto_detect_kind: bool,
398+
found_files: &Vec<SourceFileInformation>,
399+
) -> NewProjectKind {
400+
let bin_file = found_files.iter().find(|x| x.bin);
401+
402+
let kind_from_files = if !found_files.is_empty() && bin_file.is_none() {
403+
NewProjectKind::Lib
404+
} else {
405+
NewProjectKind::Bin
406+
};
407+
408+
if auto_detect_kind {
409+
return kind_from_files;
410+
}
411+
412+
requested_kind
413+
}
414+
392415
pub fn new(opts: &NewOptions, config: &Config) -> CargoResult<()> {
393416
let path = &opts.path;
394417
if path.exists() {
@@ -399,20 +422,17 @@ pub fn new(opts: &NewOptions, config: &Config) -> CargoResult<()> {
399422
)
400423
}
401424

425+
let is_bin = opts.kind.is_bin();
426+
402427
let name = get_name(path, opts)?;
403-
check_name(
404-
name,
405-
opts.name.is_none(),
406-
opts.kind.is_bin(),
407-
&mut config.shell(),
408-
)?;
428+
check_name(name, opts.name.is_none(), is_bin, &mut config.shell())?;
409429

410430
let mkopts = MkOptions {
411431
version_control: opts.version_control,
412432
path,
413433
name,
414434
source_files: vec![plan_new_source_file(opts.kind.is_bin(), name.to_string())],
415-
bin: opts.kind.is_bin(),
435+
bin: is_bin,
416436
edition: opts.edition.as_deref(),
417437
registry: opts.registry.as_deref(),
418438
};
@@ -427,7 +447,7 @@ pub fn new(opts: &NewOptions, config: &Config) -> CargoResult<()> {
427447
Ok(())
428448
}
429449

430-
pub fn init(opts: &NewOptions, config: &Config) -> CargoResult<()> {
450+
pub fn init(opts: &NewOptions, config: &Config) -> CargoResult<NewProjectKind> {
431451
// This is here just as a random location to exercise the internal error handling.
432452
if std::env::var_os("__CARGO_TEST_INTERNAL_ERROR").is_some() {
433453
return Err(crate::util::internal("internal error test"));
@@ -445,14 +465,34 @@ pub fn init(opts: &NewOptions, config: &Config) -> CargoResult<()> {
445465

446466
detect_source_paths_and_types(path, name, &mut src_paths_types)?;
447467

468+
let kind = calculate_new_project_kind(opts.kind, opts.auto_detect_kind, &src_paths_types);
469+
let has_bin = kind.is_bin();
470+
448471
if src_paths_types.is_empty() {
449-
src_paths_types.push(plan_new_source_file(opts.kind.is_bin(), name.to_string()));
450-
} else {
451-
// --bin option may be ignored if lib.rs or src/lib.rs present
452-
// Maybe when doing `cargo init --bin` inside a library package stub,
453-
// user may mean "initialize for library, but also add binary target"
472+
src_paths_types.push(plan_new_source_file(has_bin, name.to_string()));
473+
} else if src_paths_types.len() == 1 && !src_paths_types.iter().any(|x| x.bin == has_bin) {
474+
// we've found the only file and it's not the type user wants. Change the type and warn
475+
let file_type = if src_paths_types[0].bin {
476+
NewProjectKind::Bin
477+
} else {
478+
NewProjectKind::Lib
479+
};
480+
config.shell().warn(format!(
481+
"file `{}` seems to be a {} file",
482+
src_paths_types[0].relative_path, file_type
483+
))?;
484+
src_paths_types[0].bin = has_bin
485+
} else if src_paths_types.len() > 1 && !has_bin {
486+
// We have found both lib and bin files and the user would like us to treat both as libs
487+
anyhow::bail!(
488+
"cannot have a package with \
489+
multiple libraries, \
490+
found both `{}` and `{}`",
491+
src_paths_types[0].relative_path,
492+
src_paths_types[1].relative_path
493+
)
454494
}
455-
let has_bin = src_paths_types.iter().any(|x| x.bin);
495+
456496
check_name(name, opts.name.is_none(), has_bin, &mut config.shell())?;
457497

458498
let mut version_control = opts.version_control;
@@ -508,7 +548,7 @@ pub fn init(opts: &NewOptions, config: &Config) -> CargoResult<()> {
508548
path.display()
509549
)
510550
})?;
511-
Ok(())
551+
Ok(kind)
512552
}
513553

514554
/// IgnoreList

tests/testsuite/init.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,3 +598,74 @@ mod tests {
598598
"#
599599
);
600600
}
601+
602+
#[cargo_test]
603+
fn creates_binary_when_instructed_and_has_lib_file_no_warning() {
604+
let path = paths::root().join("foo");
605+
fs::create_dir(&path).unwrap();
606+
fs::write(path.join("foo.rs"), "fn not_main() {}").unwrap();
607+
cargo_process("init --bin")
608+
.cwd(&path)
609+
.with_stderr(
610+
"\
611+
[WARNING] file `foo.rs` seems to be a library file
612+
[CREATED] binary (application) package
613+
",
614+
)
615+
.run();
616+
617+
let cargo_toml = fs::read_to_string(path.join("Cargo.toml")).unwrap();
618+
assert!(cargo_toml.contains("[[bin]]"));
619+
assert!(!cargo_toml.contains("[lib]"));
620+
}
621+
622+
#[cargo_test]
623+
fn creates_library_when_instructed_and_has_bin_file() {
624+
let path = paths::root().join("foo");
625+
fs::create_dir(&path).unwrap();
626+
fs::write(path.join("foo.rs"), "fn main() {}").unwrap();
627+
cargo_process("init --lib")
628+
.cwd(&path)
629+
.with_stderr(
630+
"\
631+
[WARNING] file `foo.rs` seems to be a binary (application) file
632+
[CREATED] library package
633+
",
634+
)
635+
.run();
636+
637+
let cargo_toml = fs::read_to_string(path.join("Cargo.toml")).unwrap();
638+
assert!(!cargo_toml.contains("[[bin]]"));
639+
assert!(cargo_toml.contains("[lib]"));
640+
}
641+
642+
#[cargo_test]
643+
fn creates_binary_when_both_binlib_present() {
644+
let path = paths::root().join("foo");
645+
fs::create_dir(&path).unwrap();
646+
fs::write(path.join("foo.rs"), "fn main() {}").unwrap();
647+
fs::write(path.join("lib.rs"), "fn notmain() {}").unwrap();
648+
cargo_process("init --bin")
649+
.cwd(&path)
650+
.with_stderr("[CREATED] binary (application) package")
651+
.run();
652+
653+
let cargo_toml = fs::read_to_string(path.join("Cargo.toml")).unwrap();
654+
assert!(cargo_toml.contains("[[bin]]"));
655+
assert!(cargo_toml.contains("[lib]"));
656+
}
657+
658+
#[cargo_test]
659+
fn cant_create_library_when_both_binlib_present() {
660+
let path = paths::root().join("foo");
661+
fs::create_dir(&path).unwrap();
662+
fs::write(path.join("foo.rs"), "fn main() {}").unwrap();
663+
fs::write(path.join("lib.rs"), "fn notmain() {}").unwrap();
664+
cargo_process("init --lib")
665+
.cwd(&path)
666+
.with_status(101)
667+
.with_stderr(
668+
"[ERROR] cannot have a package with multiple libraries, found both `foo.rs` and `lib.rs`"
669+
)
670+
.run();
671+
}

0 commit comments

Comments
 (0)