Skip to content

Commit 1ee4edb

Browse files
committed
Auto merge of #4214 - msehnout:master, r=matklad
Infer multi-file binaries like `src/bin/server/main.rs` by convention This feature is described in issue #4086
2 parents d8cf602 + f08a9d3 commit 1ee4edb

File tree

3 files changed

+120
-8
lines changed

3 files changed

+120
-8
lines changed

src/cargo/util/toml.rs

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ impl Layout {
5252

5353
try_add_file(&mut bins, root_path.join("src").join("main.rs"));
5454
try_add_files(&mut bins, root_path.join("src").join("bin"));
55+
try_add_mains_from_dirs(&mut bins, root_path.join("src").join("bin"));
5556

5657
try_add_files(&mut examples, root_path.join("examples"));
5758

@@ -74,6 +75,25 @@ fn try_add_file(files: &mut Vec<PathBuf>, file: PathBuf) {
7475
files.push(file);
7576
}
7677
}
78+
79+
// Add directories form src/bin which contain main.rs file
80+
fn try_add_mains_from_dirs(files: &mut Vec<PathBuf>, root: PathBuf) {
81+
if let Ok(new) = fs::read_dir(&root) {
82+
let new: Vec<PathBuf> = new.filter_map(|i| i.ok())
83+
// Filter only directories
84+
.filter(|i| {
85+
i.file_type().map(|f| f.is_dir()).unwrap_or(false)
86+
// Convert DirEntry into PathBuf and append "main.rs"
87+
}).map(|i| {
88+
i.path().join("main.rs")
89+
// Filter only directories where main.rs is present
90+
}).filter(|f| {
91+
f.as_path().exists()
92+
}).collect();
93+
files.extend(new);
94+
}
95+
}
96+
7797
fn try_add_files(files: &mut Vec<PathBuf>, root: PathBuf) {
7898
if let Ok(new) = fs::read_dir(&root) {
7999
files.extend(new.filter_map(|dir| {
@@ -505,7 +525,18 @@ fn inferred_bin_targets(name: &str, layout: &Layout) -> Vec<TomlTarget> {
505525
*bin == layout.root.join("src").join("main.rs") {
506526
Some(name.to_string())
507527
} else {
508-
bin.file_stem().and_then(|s| s.to_str()).map(|f| f.to_string())
528+
// bin is either a source file or a directory with main.rs inside.
529+
if bin.ends_with("main.rs") && !bin.ends_with("src/bin/main.rs") {
530+
if let Some(parent) = bin.parent() {
531+
// Use a name of this directory as a name for binary
532+
parent.file_stem().and_then(|s| s.to_str()).map(|f| f.to_string())
533+
} else {
534+
None
535+
}
536+
} else {
537+
// regular case, just a file in the bin directory
538+
bin.file_stem().and_then(|s| s.to_str()).map(|f| f.to_string())
539+
}
509540
};
510541

511542
name.map(|name| {
@@ -1444,9 +1475,9 @@ fn inferred_bin_path(bin: &TomlBinTarget,
14441475
package_root: &Path,
14451476
bin_len: usize) -> PathBuf {
14461477
// here we have a single bin, so it may be located in src/main.rs, src/foo.rs,
1447-
// srb/bin/foo.rs or src/bin/main.rs
1478+
// src/bin/foo.rs, src/bin/foo/main.rs or src/bin/main.rs
14481479
if bin_len == 1 {
1449-
let path = Path::new("src").join(&format!("main.rs"));
1480+
let path = Path::new("src").join("main.rs");
14501481
if package_root.join(&path).exists() {
14511482
return path.to_path_buf()
14521483
}
@@ -1463,7 +1494,13 @@ fn inferred_bin_path(bin: &TomlBinTarget,
14631494
return path.to_path_buf()
14641495
}
14651496

1466-
return Path::new("src").join("bin").join(&format!("main.rs")).to_path_buf()
1497+
// check for the case where src/bin/foo/main.rs is present
1498+
let path = Path::new("src").join("bin").join(bin.name()).join("main.rs");
1499+
if package_root.join(&path).exists() {
1500+
return path.to_path_buf()
1501+
}
1502+
1503+
return Path::new("src").join("bin").join("main.rs").to_path_buf()
14671504
}
14681505

14691506
// bin_len > 1
@@ -1472,19 +1509,25 @@ fn inferred_bin_path(bin: &TomlBinTarget,
14721509
return path.to_path_buf()
14731510
}
14741511

1512+
// we can also have src/bin/foo/main.rs, but the former one is preferred
1513+
let path = Path::new("src").join("bin").join(bin.name()).join("main.rs");
1514+
if package_root.join(&path).exists() {
1515+
return path.to_path_buf()
1516+
}
1517+
14751518
if !has_lib {
14761519
let path = Path::new("src").join(&format!("{}.rs", bin.name()));
14771520
if package_root.join(&path).exists() {
14781521
return path.to_path_buf()
14791522
}
14801523
}
14811524

1482-
let path = Path::new("src").join("bin").join(&format!("main.rs"));
1525+
let path = Path::new("src").join("bin").join("main.rs");
14831526
if package_root.join(&path).exists() {
14841527
return path.to_path_buf()
14851528
}
14861529

1487-
return Path::new("src").join(&format!("main.rs")).to_path_buf()
1530+
return Path::new("src").join("main.rs").to_path_buf()
14881531
}
14891532

14901533
fn build_profiles(profiles: &Option<TomlProfiles>) -> Profiles {

src/doc/manifest.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -458,8 +458,11 @@ Most of the time workspaces will not need to be dealt with as `cargo new` and
458458
If your project is an executable, name the main source file `src/main.rs`. If it
459459
is a library, name the main source file `src/lib.rs`.
460460

461-
Cargo will also treat any files located in `src/bin/*.rs` as executables. Do
462-
note, however, once you add a `[[bin]]` section ([see
461+
Cargo will also treat any files located in `src/bin/*.rs` as executables. If your
462+
executable consist of more than just one source file, you might also use a directory
463+
inside `src/bin` containing a `main.rs` file which will be treated as an executable
464+
with a name of the parent directory.
465+
Do note, however, once you add a `[[bin]]` section ([see
463466
below](#configuring-a-target)), Cargo will no longer automatically build files
464467
located in `src/bin/*.rs`. Instead you must create a `[[bin]]` section for
465468
each file you want to build.
@@ -474,6 +477,8 @@ integration tests, and benchmarks respectively.
474477
main.rs # the main entry point for projects producing executables
475478
▾ bin/ # (optional) directory containing additional executables
476479
*.rs
480+
▾ */ # (optional) directories containing multi-file executables
481+
main.rs
477482
▾ examples/ # (optional) examples
478483
*.rs
479484
▾ tests/ # (optional) integration tests

tests/build.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3281,3 +3281,67 @@ fn no_bin_in_src_with_lib() {
32813281
execs().with_status(101)
32823282
.with_stderr_contains(r#"[ERROR] couldn't read "[..]main.rs"[..]"#));
32833283
}
3284+
3285+
3286+
#[test]
3287+
fn dirs_in_bin_dir_with_main_rs() {
3288+
let p = project("foo")
3289+
.file("Cargo.toml", r#"
3290+
[package]
3291+
name = "foo"
3292+
version = "0.1.0"
3293+
authors = []
3294+
"#)
3295+
.file("src/main.rs", "fn main() {}")
3296+
.file("src/bin/bar.rs", "fn main() {}")
3297+
.file("src/bin/bar2.rs", "fn main() {}")
3298+
.file("src/bin/bar3/main.rs", "fn main() {}")
3299+
.file("src/bin/bar4/main.rs", "fn main() {}");
3300+
3301+
assert_that(p.cargo_process("build"), execs().with_status(0));
3302+
assert_that(&p.bin("foo"), existing_file());
3303+
assert_that(&p.bin("bar"), existing_file());
3304+
assert_that(&p.bin("bar2"), existing_file());
3305+
assert_that(&p.bin("bar3"), existing_file());
3306+
assert_that(&p.bin("bar4"), existing_file());
3307+
}
3308+
3309+
#[test]
3310+
fn dir_and_file_with_same_name_in_bin() {
3311+
// this should fail, because we have two binaries with the same name
3312+
let p = project("bar")
3313+
.file("Cargo.toml", r#"
3314+
[package]
3315+
name = "bar"
3316+
version = "0.1.0"
3317+
authors = []
3318+
"#)
3319+
.file("src/main.rs", "fn main() {}")
3320+
.file("src/bin/foo.rs", "fn main() {}")
3321+
.file("src/bin/foo/main.rs", "fn main() {}");
3322+
3323+
assert_that(p.cargo_process("build"),
3324+
execs().with_status(101)
3325+
.with_stderr_contains("\
3326+
[..]found duplicate binary name foo, but all binary targets must have a unique name[..]
3327+
"));
3328+
}
3329+
3330+
#[test]
3331+
fn inferred_path_in_src_bin_foo() {
3332+
let p = project("foo")
3333+
.file("Cargo.toml", r#"
3334+
[package]
3335+
name = "foo"
3336+
version = "0.1.0"
3337+
authors = []
3338+
3339+
[[bin]]
3340+
name = "bar"
3341+
# Note, no `path` key!
3342+
"#)
3343+
.file("src/bin/bar/main.rs", "fn main() {}");
3344+
3345+
assert_that(p.cargo_process("build"), execs().with_status(0));
3346+
assert_that(&p.bin("bar"), existing_file());
3347+
}

0 commit comments

Comments
 (0)