Skip to content

Commit

Permalink
Auto merge of #4214 - msehnout:master, r=matklad
Browse files Browse the repository at this point in the history
Infer multi-file binaries like `src/bin/server/main.rs` by convention

This feature is described in issue #4086
  • Loading branch information
bors committed Jun 30, 2017
2 parents d8cf602 + f08a9d3 commit 1ee4edb
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 8 deletions.
55 changes: 49 additions & 6 deletions src/cargo/util/toml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ impl Layout {

try_add_file(&mut bins, root_path.join("src").join("main.rs"));
try_add_files(&mut bins, root_path.join("src").join("bin"));
try_add_mains_from_dirs(&mut bins, root_path.join("src").join("bin"));

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

Expand All @@ -74,6 +75,25 @@ fn try_add_file(files: &mut Vec<PathBuf>, file: PathBuf) {
files.push(file);
}
}

// Add directories form src/bin which contain main.rs file
fn try_add_mains_from_dirs(files: &mut Vec<PathBuf>, root: PathBuf) {
if let Ok(new) = fs::read_dir(&root) {
let new: Vec<PathBuf> = new.filter_map(|i| i.ok())
// Filter only directories
.filter(|i| {
i.file_type().map(|f| f.is_dir()).unwrap_or(false)
// Convert DirEntry into PathBuf and append "main.rs"
}).map(|i| {
i.path().join("main.rs")
// Filter only directories where main.rs is present
}).filter(|f| {
f.as_path().exists()
}).collect();
files.extend(new);
}
}

fn try_add_files(files: &mut Vec<PathBuf>, root: PathBuf) {
if let Ok(new) = fs::read_dir(&root) {
files.extend(new.filter_map(|dir| {
Expand Down Expand Up @@ -505,7 +525,18 @@ fn inferred_bin_targets(name: &str, layout: &Layout) -> Vec<TomlTarget> {
*bin == layout.root.join("src").join("main.rs") {
Some(name.to_string())
} else {
bin.file_stem().and_then(|s| s.to_str()).map(|f| f.to_string())
// bin is either a source file or a directory with main.rs inside.
if bin.ends_with("main.rs") && !bin.ends_with("src/bin/main.rs") {
if let Some(parent) = bin.parent() {
// Use a name of this directory as a name for binary
parent.file_stem().and_then(|s| s.to_str()).map(|f| f.to_string())
} else {
None
}
} else {
// regular case, just a file in the bin directory
bin.file_stem().and_then(|s| s.to_str()).map(|f| f.to_string())
}
};

name.map(|name| {
Expand Down Expand Up @@ -1444,9 +1475,9 @@ fn inferred_bin_path(bin: &TomlBinTarget,
package_root: &Path,
bin_len: usize) -> PathBuf {
// here we have a single bin, so it may be located in src/main.rs, src/foo.rs,
// srb/bin/foo.rs or src/bin/main.rs
// src/bin/foo.rs, src/bin/foo/main.rs or src/bin/main.rs
if bin_len == 1 {
let path = Path::new("src").join(&format!("main.rs"));
let path = Path::new("src").join("main.rs");
if package_root.join(&path).exists() {
return path.to_path_buf()
}
Expand All @@ -1463,7 +1494,13 @@ fn inferred_bin_path(bin: &TomlBinTarget,
return path.to_path_buf()
}

return Path::new("src").join("bin").join(&format!("main.rs")).to_path_buf()
// check for the case where src/bin/foo/main.rs is present
let path = Path::new("src").join("bin").join(bin.name()).join("main.rs");
if package_root.join(&path).exists() {
return path.to_path_buf()
}

return Path::new("src").join("bin").join("main.rs").to_path_buf()
}

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

// we can also have src/bin/foo/main.rs, but the former one is preferred
let path = Path::new("src").join("bin").join(bin.name()).join("main.rs");
if package_root.join(&path).exists() {
return path.to_path_buf()
}

if !has_lib {
let path = Path::new("src").join(&format!("{}.rs", bin.name()));
if package_root.join(&path).exists() {
return path.to_path_buf()
}
}

let path = Path::new("src").join("bin").join(&format!("main.rs"));
let path = Path::new("src").join("bin").join("main.rs");
if package_root.join(&path).exists() {
return path.to_path_buf()
}

return Path::new("src").join(&format!("main.rs")).to_path_buf()
return Path::new("src").join("main.rs").to_path_buf()
}

fn build_profiles(profiles: &Option<TomlProfiles>) -> Profiles {
Expand Down
9 changes: 7 additions & 2 deletions src/doc/manifest.md
Original file line number Diff line number Diff line change
Expand Up @@ -458,8 +458,11 @@ Most of the time workspaces will not need to be dealt with as `cargo new` and
If your project is an executable, name the main source file `src/main.rs`. If it
is a library, name the main source file `src/lib.rs`.

Cargo will also treat any files located in `src/bin/*.rs` as executables. Do
note, however, once you add a `[[bin]]` section ([see
Cargo will also treat any files located in `src/bin/*.rs` as executables. If your
executable consist of more than just one source file, you might also use a directory
inside `src/bin` containing a `main.rs` file which will be treated as an executable
with a name of the parent directory.
Do note, however, once you add a `[[bin]]` section ([see
below](#configuring-a-target)), Cargo will no longer automatically build files
located in `src/bin/*.rs`. Instead you must create a `[[bin]]` section for
each file you want to build.
Expand All @@ -474,6 +477,8 @@ integration tests, and benchmarks respectively.
main.rs # the main entry point for projects producing executables
▾ bin/ # (optional) directory containing additional executables
*.rs
▾ */ # (optional) directories containing multi-file executables
main.rs
▾ examples/ # (optional) examples
*.rs
▾ tests/ # (optional) integration tests
Expand Down
64 changes: 64 additions & 0 deletions tests/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3281,3 +3281,67 @@ fn no_bin_in_src_with_lib() {
execs().with_status(101)
.with_stderr_contains(r#"[ERROR] couldn't read "[..]main.rs"[..]"#));
}


#[test]
fn dirs_in_bin_dir_with_main_rs() {
let p = project("foo")
.file("Cargo.toml", r#"
[package]
name = "foo"
version = "0.1.0"
authors = []
"#)
.file("src/main.rs", "fn main() {}")
.file("src/bin/bar.rs", "fn main() {}")
.file("src/bin/bar2.rs", "fn main() {}")
.file("src/bin/bar3/main.rs", "fn main() {}")
.file("src/bin/bar4/main.rs", "fn main() {}");

assert_that(p.cargo_process("build"), execs().with_status(0));
assert_that(&p.bin("foo"), existing_file());
assert_that(&p.bin("bar"), existing_file());
assert_that(&p.bin("bar2"), existing_file());
assert_that(&p.bin("bar3"), existing_file());
assert_that(&p.bin("bar4"), existing_file());
}

#[test]
fn dir_and_file_with_same_name_in_bin() {
// this should fail, because we have two binaries with the same name
let p = project("bar")
.file("Cargo.toml", r#"
[package]
name = "bar"
version = "0.1.0"
authors = []
"#)
.file("src/main.rs", "fn main() {}")
.file("src/bin/foo.rs", "fn main() {}")
.file("src/bin/foo/main.rs", "fn main() {}");

assert_that(p.cargo_process("build"),
execs().with_status(101)
.with_stderr_contains("\
[..]found duplicate binary name foo, but all binary targets must have a unique name[..]
"));
}

#[test]
fn inferred_path_in_src_bin_foo() {
let p = project("foo")
.file("Cargo.toml", r#"
[package]
name = "foo"
version = "0.1.0"
authors = []
[[bin]]
name = "bar"
# Note, no `path` key!
"#)
.file("src/bin/bar/main.rs", "fn main() {}");

assert_that(p.cargo_process("build"), execs().with_status(0));
assert_that(&p.bin("bar"), existing_file());
}

0 comments on commit 1ee4edb

Please sign in to comment.