Skip to content

Commit 3f8fc0e

Browse files
committed
respect user choice of lib/bin over heuristics
1 parent b1684e2 commit 3f8fc0e

File tree

4 files changed

+135
-23
lines changed

4 files changed

+135
-23
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/bin/cargo/commands/new.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ pub fn cli() -> App {
1515
pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
1616
let opts = args.new_options(config)?;
1717

18-
ops::new(&opts, config)?;
18+
let project_kind = ops::new(&opts, config)?;
1919
let path = args.value_of("path").unwrap();
2020
let package_name = if let Some(name) = args.value_of("name") {
2121
name
@@ -24,7 +24,7 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
2424
};
2525
config.shell().status(
2626
"Created",
27-
format!("{} `{}` package", opts.kind, package_name),
27+
format!("{} `{}` package", project_kind, package_name),
2828
)?;
2929
Ok(())
3030
}

src/cargo/ops/cargo_new.rs

Lines changed: 66 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -62,19 +62,25 @@ pub struct NewOptions {
6262
pub enum NewProjectKind {
6363
Bin,
6464
Lib,
65+
Auto,
6566
}
6667

6768
impl NewProjectKind {
6869
fn is_bin(self) -> bool {
6970
self == NewProjectKind::Bin
7071
}
72+
73+
fn is_auto(self) -> bool {
74+
self == NewProjectKind::Auto
75+
}
7176
}
7277

7378
impl fmt::Display for NewProjectKind {
7479
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
7580
match *self {
7681
NewProjectKind::Bin => "binary (application)",
7782
NewProjectKind::Lib => "library",
83+
NewProjectKind::Auto => "auto-select type",
7884
}
7985
.fmt(f)
8086
}
@@ -109,8 +115,8 @@ impl NewOptions {
109115
let kind = match (bin, lib) {
110116
(true, true) => anyhow::bail!("can't specify both lib and binary outputs"),
111117
(false, true) => NewProjectKind::Lib,
112-
// default to bin
113-
(_, false) => NewProjectKind::Bin,
118+
(true, false) => NewProjectKind::Bin,
119+
(false, false) => NewProjectKind::Auto,
114120
};
115121

116122
let opts = NewOptions {
@@ -389,7 +395,26 @@ fn plan_new_source_file(bin: bool, package_name: String) -> SourceFileInformatio
389395
}
390396
}
391397

392-
pub fn new(opts: &NewOptions, config: &Config) -> CargoResult<()> {
398+
fn calculate_new_project_kind(
399+
requested_kind: NewProjectKind,
400+
found_files: &Vec<SourceFileInformation>,
401+
) -> NewProjectKind {
402+
let bin_file = found_files.iter().find(|x| x.bin);
403+
404+
let kind_from_files = if !found_files.is_empty() && bin_file.is_none() {
405+
NewProjectKind::Lib
406+
} else {
407+
NewProjectKind::Bin
408+
};
409+
410+
if requested_kind.is_auto() {
411+
return kind_from_files;
412+
}
413+
414+
requested_kind
415+
}
416+
417+
pub fn new(opts: &NewOptions, config: &Config) -> CargoResult<NewProjectKind> {
393418
let path = &opts.path;
394419
if path.exists() {
395420
anyhow::bail!(
@@ -399,20 +424,22 @@ pub fn new(opts: &NewOptions, config: &Config) -> CargoResult<()> {
399424
)
400425
}
401426

427+
let kind = match opts.kind {
428+
NewProjectKind::Bin => NewProjectKind::Bin,
429+
NewProjectKind::Auto => NewProjectKind::Bin,
430+
_ => NewProjectKind::Lib,
431+
};
432+
let is_bin = kind.is_bin();
433+
402434
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-
)?;
435+
check_name(name, opts.name.is_none(), is_bin, &mut config.shell())?;
409436

410437
let mkopts = MkOptions {
411438
version_control: opts.version_control,
412439
path,
413440
name,
414441
source_files: vec![plan_new_source_file(opts.kind.is_bin(), name.to_string())],
415-
bin: opts.kind.is_bin(),
442+
bin: is_bin,
416443
edition: opts.edition.as_deref(),
417444
registry: opts.registry.as_deref(),
418445
};
@@ -424,10 +451,10 @@ pub fn new(opts: &NewOptions, config: &Config) -> CargoResult<()> {
424451
path.display()
425452
)
426453
})?;
427-
Ok(())
454+
Ok(kind)
428455
}
429456

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

446473
detect_source_paths_and_types(path, name, &mut src_paths_types)?;
447474

475+
let kind = calculate_new_project_kind(opts.kind, &src_paths_types);
476+
let has_bin = kind.is_bin();
477+
448478
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"
479+
src_paths_types.push(plan_new_source_file(has_bin, name.to_string()));
480+
} else if src_paths_types.len() == 1 && !src_paths_types.iter().any(|x| x.bin == has_bin) {
481+
// we've found the only file and it's not the type user wants. Change the type and warn
482+
let file_type = if src_paths_types[0].bin {
483+
NewProjectKind::Bin.to_string()
484+
} else {
485+
NewProjectKind::Lib.to_string()
486+
};
487+
config.shell().warn(format!(
488+
"file '{}' seems to be a {} file",
489+
src_paths_types[0].relative_path, file_type
490+
))?;
491+
src_paths_types[0].bin = has_bin
492+
} else if src_paths_types.len() > 1 && !has_bin {
493+
// We have found both lib and bin files and the user would like us to treat both as libs
494+
anyhow::bail!(
495+
"cannot have a package with \
496+
multiple libraries, \
497+
found both `{}` and `{}`",
498+
src_paths_types[0].relative_path,
499+
src_paths_types[1].relative_path
500+
)
454501
}
455-
let has_bin = src_paths_types.iter().any(|x| x.bin);
502+
456503
check_name(name, opts.name.is_none(), has_bin, &mut config.shell())?;
457504

458505
let mut version_control = opts.version_control;
@@ -508,7 +555,7 @@ pub fn init(opts: &NewOptions, config: &Config) -> CargoResult<()> {
508555
path.display()
509556
)
510557
})?;
511-
Ok(())
558+
Ok(kind)
512559
}
513560

514561
/// IgnoreList

tests/testsuite/init.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,3 +598,68 @@ 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+
"[WARNING] file 'foo.rs' seems to be a library file\n[CREATED] binary (application) package"
611+
)
612+
.run();
613+
614+
let cargo_toml = fs::read_to_string(path.join("Cargo.toml")).unwrap();
615+
assert!(cargo_toml.contains("[[bin]]"));
616+
assert!(!cargo_toml.contains("[lib]"));
617+
}
618+
619+
#[cargo_test]
620+
fn creates_library_when_instructed_and_has_bin_file() {
621+
let path = paths::root().join("foo");
622+
fs::create_dir(&path).unwrap();
623+
fs::write(path.join("foo.rs"), "fn main() {}").unwrap();
624+
cargo_process("init --lib")
625+
.cwd(&path)
626+
.with_stderr(
627+
"[WARNING] file 'foo.rs' seems to be a binary (application) file\n[CREATED] library package"
628+
)
629+
.run();
630+
631+
let cargo_toml = fs::read_to_string(path.join("Cargo.toml")).unwrap();
632+
assert!(!cargo_toml.contains("[[bin]]"));
633+
assert!(cargo_toml.contains("[lib]"));
634+
}
635+
636+
#[cargo_test]
637+
fn creates_binary_when_both_binlib_present() {
638+
let path = paths::root().join("foo");
639+
fs::create_dir(&path).unwrap();
640+
fs::write(path.join("foo.rs"), "fn main() {}").unwrap();
641+
fs::write(path.join("lib.rs"), "fn notmain() {}").unwrap();
642+
cargo_process("init --bin")
643+
.cwd(&path)
644+
.with_stderr("[CREATED] binary (application) package")
645+
.run();
646+
647+
let cargo_toml = fs::read_to_string(path.join("Cargo.toml")).unwrap();
648+
assert!(cargo_toml.contains("[[bin]]"));
649+
assert!(cargo_toml.contains("[lib]"));
650+
}
651+
652+
#[cargo_test]
653+
fn cant_create_library_when_both_binlib_present() {
654+
let path = paths::root().join("foo");
655+
fs::create_dir(&path).unwrap();
656+
fs::write(path.join("foo.rs"), "fn main() {}").unwrap();
657+
fs::write(path.join("lib.rs"), "fn notmain() {}").unwrap();
658+
cargo_process("init --lib")
659+
.cwd(&path)
660+
.with_status(101)
661+
.with_stderr(
662+
"[ERROR] cannot have a package with multiple libraries, found both `foo.rs` and `lib.rs`"
663+
)
664+
.run();
665+
}

0 commit comments

Comments
 (0)