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

Infer multi-file binaries like src/bin/server/main.rs by convention #4214

Merged
merged 5 commits into from
Jun 30, 2017
Merged
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
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 @@ -457,8 +457,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 @@ -473,6 +476,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 @@ -3271,3 +3271,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());
}