diff --git a/src/bin/cargo-build.rs b/src/bin/cargo-build.rs index 04e2ec790..d0530615c 100755 --- a/src/bin/cargo-build.rs +++ b/src/bin/cargo-build.rs @@ -49,9 +49,6 @@ fn execute(options: Options, shell: &mut MultiShell) -> CliResult> { })) }; - let update = options.update_remotes; - let jobs = options.jobs; - let env = if options.release { "release" } else { diff --git a/src/bin/cargo-test.rs b/src/bin/cargo-test.rs index 79624e47c..257b7024e 100755 --- a/src/bin/cargo-test.rs +++ b/src/bin/cargo-test.rs @@ -9,7 +9,7 @@ extern crate serialize; extern crate hammer; use std::os; -use std::io::fs; +use std::io::{UserExecute, fs}; use cargo::ops; use cargo::{execute_main_without_stdin}; @@ -65,7 +65,7 @@ fn execute(options: Options, shell: &mut MultiShell) -> CliResult> { // TODO: The proper fix is to have target knows its expected // output and only run expected executables. if file.display().to_str().as_slice().contains("dSYM") { continue; } - if !file.is_file() { continue; } + if !is_executable(&file) { continue; } try!(util::process(file).exec().map_err(|e| { CliError::from_boxed(e.box_error(), 1) @@ -74,3 +74,8 @@ fn execute(options: Options, shell: &mut MultiShell) -> CliResult> { Ok(None) } + +fn is_executable(path: &Path) -> bool { + if !path.is_file() { return false; } + path.stat().map(|stat| stat.perm.intersects(UserExecute)).unwrap_or(false) +} diff --git a/src/cargo/core/manifest.rs b/src/cargo/core/manifest.rs index 2f08c18e6..048ef6ada 100644 --- a/src/cargo/core/manifest.rs +++ b/src/cargo/core/manifest.rs @@ -310,6 +310,10 @@ impl Target { } } + pub fn get_name<'a>(&'a self) -> &'a str { + self.name.as_slice() + } + pub fn get_path<'a>(&'a self) -> &'a Path { &self.path } diff --git a/src/cargo/ops/cargo_read_manifest.rs b/src/cargo/ops/cargo_read_manifest.rs index 83eeca006..cd40e866a 100644 --- a/src/cargo/ops/cargo_read_manifest.rs +++ b/src/cargo/ops/cargo_read_manifest.rs @@ -4,11 +4,12 @@ use util; use core::{Package,Manifest,SourceId}; use util::{CargoResult, human}; use util::important_paths::find_project_manifest_exact; +use util::toml::{Layout, project_layout}; -pub fn read_manifest(contents: &[u8], source_id: &SourceId) +pub fn read_manifest(contents: &[u8], layout: Layout, source_id: &SourceId) -> CargoResult<(Manifest, Vec)> { - util::toml::to_manifest(contents, source_id).map_err(human) + util::toml::to_manifest(contents, source_id, layout).map_err(human) } pub fn read_package(path: &Path, source_id: &SourceId) @@ -17,8 +18,10 @@ pub fn read_package(path: &Path, source_id: &SourceId) log!(5, "read_package; path={}; source-id={}", path.display(), source_id); let mut file = try!(File::open(path)); let data = try!(file.read_to_end()); - let (manifest, nested) = try!(read_manifest(data.as_slice(), - source_id)); + + let layout = project_layout(&path.dir_path()); + let (manifest, nested) = + try!(read_manifest(data.as_slice(), layout, source_id)); Ok((Package::new(manifest, path, source_id), nested)) } diff --git a/src/cargo/ops/cargo_rustc.rs b/src/cargo/ops/cargo_rustc.rs index 50b2d2fc4..57cbdcfed 100644 --- a/src/cargo/ops/cargo_rustc.rs +++ b/src/cargo/ops/cargo_rustc.rs @@ -218,7 +218,7 @@ fn rustc(root: &Path, target: &Target, cx: &mut Context) -> Job { log!(5, "command={}", rustc); - cx.config.shell().verbose(|shell| shell.status("Running", rustc.to_str())); + let _ = cx.config.shell().verbose(|shell| shell.status("Running", rustc.to_str())); proc() { if primary { @@ -248,12 +248,16 @@ fn build_base_args(into: &mut Args, target: &Target, crate_types: Vec<&str>, cx: &Context) { // TODO: Handle errors in converting paths into args into.push(target.get_path().display().to_str()); + + into.push("--crate-name".to_str()); + into.push(target.get_name().to_str()); + for crate_type in crate_types.iter() { into.push("--crate-type".to_str()); into.push(crate_type.to_str()); } - let mut out = cx.dest.clone(); + let out = cx.dest.clone(); let profile = target.get_profile(); if profile.get_opt_level() != 0 { @@ -269,8 +273,13 @@ fn build_base_args(into: &mut Args, target: &Target, crate_types: Vec<&str>, into.push("--test".to_str()); } - into.push("--out-dir".to_str()); - into.push(out.display().to_str()); + if target.is_lib() { + into.push("--out-dir".to_str()); + into.push(out.display().to_str()); + } else { + into.push("-o".to_str()); + into.push(out.join(target.get_name()).display().to_str()); + } } fn build_deps_args(dst: &mut Args, cx: &Context) { diff --git a/src/cargo/util/toml.rs b/src/cargo/util/toml.rs index d12762ba1..701a52746 100644 --- a/src/cargo/util/toml.rs +++ b/src/cargo/util/toml.rs @@ -1,6 +1,7 @@ use serialize::Decodable; use std::collections::HashMap; use std::str; +use std::io::fs; use toml; use core::{SourceId, GitKind}; @@ -9,8 +10,52 @@ use core::{Summary, Manifest, Target, Dependency, PackageId}; use core::source::Location; use util::{CargoResult, Require, human}; +#[deriving(Clone)] +pub struct Layout { + lib: Option, + bins: Vec +} + +impl Layout { + fn main<'a>(&'a self) -> Option<&'a Path> { + self.bins.iter().find(|p| { + match p.filename_str() { + Some(s) => s == "main.rs", + None => false + } + }) + } +} + +pub fn project_layout(root: &Path) -> Layout { + let mut lib = None; + let mut bins = vec!(); + + if root.join("src/lib.rs").exists() { + lib = Some(root.join("src/lib.rs")); + } + + if root.join("src/main.rs").exists() { + bins.push(root.join("src/main.rs")); + } + + fs::readdir(&root.join("src/bin")) + .map(|v| v.move_iter()) + .map(|i| i.filter(|f| f.extension_str() == Some("rs"))) + .map(|mut i| i.collect()) + .map(|found| bins.push_all_move(found)); + + Layout { + lib: lib, + bins: bins + } +} + pub fn to_manifest(contents: &[u8], - source_id: &SourceId) -> CargoResult<(Manifest, Vec)> { + source_id: &SourceId, + layout: Layout) + -> CargoResult<(Manifest, Vec)> +{ let contents = try!(str::from_utf8(contents).require(|| { human("Cargo.toml is not valid UTF-8") })); @@ -22,7 +67,7 @@ pub fn to_manifest(contents: &[u8], manifest\n\n{}", e))) }; - let pair = try!(toml_manifest.to_manifest(source_id).map_err(|err| { + let pair = try!(toml_manifest.to_manifest(source_id, &layout).map_err(|err| { human(format!("Cargo.toml is not a valid manifest\n\n{}", err)) })); let (mut manifest, paths) = pair; @@ -55,8 +100,6 @@ pub fn to_manifest(contents: &[u8], _ => m.add_unused_key(key), } } - - } pub fn parse(toml: &str, file: &str) -> CargoResult { @@ -145,16 +188,98 @@ struct Context<'a> { nested_paths: &'a mut Vec } +// These functions produce the equivalent of specific manifest entries. One +// wrinkle is that certain paths cannot be represented in the manifest due +// to Toml's UTF-8 requirement. This could, in theory, mean that certain +// otherwise acceptable executable names are not used when inside of +// `src/bin/*`, but it seems ok to not build executables with non-UTF8 +// paths. +fn inferred_lib_target(name: &str, layout: &Layout) -> Option> { + layout.lib.as_ref().map(|lib| { + vec![TomlTarget { + name: name.to_str(), + crate_type: None, + path: Some(lib.display().to_str()), + test: None + }] + }) +} + +fn inferred_bin_targets(name: &str, layout: &Layout) -> Option> { + Some(layout.bins.iter().filter_map(|bin| { + let name = if bin.as_str() == Some("src/main.rs") { + Some(name.to_str()) + } else { + bin.filestem_str().map(|f| f.to_str()) + }; + + name.map(|name| { + TomlTarget { + name: name, + crate_type: None, + path: Some(bin.display().to_str()), + test: None + } + }) + }).collect()) +} + impl TomlManifest { - pub fn to_manifest(&self, source_id: &SourceId) + pub fn to_manifest(&self, source_id: &SourceId, layout: &Layout) -> CargoResult<(Manifest, Vec)> { let mut sources = vec!(); let mut nested_paths = vec!(); + let project = self.project.as_ref().or_else(|| self.package.as_ref()); + let project = try!(project.require(|| { + human("No `package` or `project` section found.") + })); + + + // If we have no lib at all, use the inferred lib if available + // If we have a lib with a path, we're done + // If we have a lib with no path, use the inferred lib or_else package name + + let lib = if self.lib.is_none() || self.lib.get_ref().is_empty() { + inferred_lib_target(project.name.as_slice(), layout) + } else { + Some(self.lib.get_ref().iter().map(|t| { + if layout.lib.is_some() && t.path.is_none() { + TomlTarget { + name: t.name.clone(), + crate_type: t.crate_type.clone(), + path: layout.lib.as_ref().map(|p| p.display().to_str()), + test: t.test + } + } else { + t.clone() + } + }).collect()) + }; + + let bins = if self.bin.is_none() || self.bin.get_ref().is_empty() { + inferred_bin_targets(project.name.as_slice(), layout) + } else { + let bin = layout.main(); + + Some(self.bin.get_ref().iter().map(|t| { + if bin.is_some() && t.path.is_none() { + TomlTarget { + name: t.name.clone(), + crate_type: t.crate_type.clone(), + path: bin.as_ref().map(|p| p.display().to_str()), + test: t.test + } + } else { + t.clone() + } + }).collect()) + }; + // Get targets - let targets = normalize(self.lib.as_ref().map(|l| l.as_slice()), - self.bin.as_ref().map(|b| b.as_slice())); + let targets = normalize(lib.as_ref().map(|l| l.as_slice()), + bins.as_ref().map(|b| b.as_slice())); if targets.is_empty() { debug!("manifest has no build targets; project={}", self.project); @@ -176,11 +301,6 @@ impl TomlManifest { try!(process_dependencies(&mut cx, true, self.dev_dependencies.as_ref())); } - let project = self.project.as_ref().or_else(|| self.package.as_ref()); - let project = try!(project.require(|| { - human("No `package` or `project` section found.") - })); - let pkgid = try!(project.to_package_id(source_id.get_location())); let summary = Summary::new(&pkgid, deps.as_slice()); Ok((Manifest::new( @@ -257,7 +377,9 @@ struct TomlTarget { } fn normalize(lib: Option<&[TomlLibTarget]>, - bin: Option<&[TomlBinTarget]>) -> Vec { + bin: Option<&[TomlBinTarget]>) + -> Vec +{ log!(4, "normalizing toml targets; lib={}; bin={}", lib, bin); fn target_profiles(target: &TomlTarget) -> Vec { diff --git a/tests/test_cargo_compile.rs b/tests/test_cargo_compile.rs index a0e2010ba..0cc9214ee 100644 --- a/tests/test_cargo_compile.rs +++ b/tests/test_cargo_compile.rs @@ -92,8 +92,8 @@ test!(cargo_compile_with_invalid_code { {filename}:1 invalid rust code! ^~~~~~~ Could not execute process \ -`rustc {filename} --crate-type bin -g --out-dir {} -L {} -L {}` (status=101)\n", - target.display(), +`rustc {filename} --crate-name foo --crate-type bin -g -o {} -L {} -L {}` (status=101)\n", + target.join("foo").display(), target.display(), target.join("deps").display(), filename = format!("src{}foo.rs", path::SEP)).as_slice())); @@ -176,6 +176,142 @@ test!(cargo_compile_with_warnings_in_a_dep_package { execs().with_stdout("test passed\n")); }) +test!(cargo_compile_with_nested_deps_inferred { + let mut p = project("foo"); + let bar = p.root().join("bar"); + let baz = p.root().join("baz"); + + p = p + .file(".cargo/config", format!(r#" + paths = ["{}", "{}"] + "#, escape_path(&bar), escape_path(&baz)).as_slice()) + .file("Cargo.toml", r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [dependencies] + + bar = "0.5.0" + + [[bin]] + + name = "foo" + "#) + .file("src/foo.rs", + main_file(r#""{}", bar::gimme()"#, ["bar"]).as_slice()) + .file("bar/Cargo.toml", r#" + [project] + + name = "bar" + version = "0.5.0" + authors = ["wycats@example.com"] + + [dependencies] + + baz = "0.5.0" + "#) + .file("bar/src/lib.rs", r#" + extern crate baz; + + pub fn gimme() -> String { + baz::gimme() + } + "#) + .file("baz/Cargo.toml", r#" + [project] + + name = "baz" + version = "0.5.0" + authors = ["wycats@example.com"] + "#) + .file("baz/src/lib.rs", r#" + pub fn gimme() -> String { + "test passed".to_str() + } + "#); + + p.cargo_process("cargo-build") + .exec_with_output() + .assert(); + + assert_that(&p.bin("foo"), existing_file()); + + assert_that( + cargo::util::process(p.bin("foo")), + execs().with_stdout("test passed\n")); +}) + +test!(cargo_compile_with_nested_deps_correct_bin { + let mut p = project("foo"); + let bar = p.root().join("bar"); + let baz = p.root().join("baz"); + + p = p + .file(".cargo/config", format!(r#" + paths = ["{}", "{}"] + "#, escape_path(&bar), escape_path(&baz)).as_slice()) + .file("Cargo.toml", r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [dependencies] + + bar = "0.5.0" + + [[bin]] + + name = "foo" + "#) + .file("src/main.rs", + main_file(r#""{}", bar::gimme()"#, ["bar"]).as_slice()) + .file("bar/Cargo.toml", r#" + [project] + + name = "bar" + version = "0.5.0" + authors = ["wycats@example.com"] + + [dependencies] + + baz = "0.5.0" + "#) + .file("bar/src/lib.rs", r#" + extern crate baz; + + pub fn gimme() -> String { + baz::gimme() + } + "#) + .file("baz/Cargo.toml", r#" + [project] + + name = "baz" + version = "0.5.0" + authors = ["wycats@example.com"] + "#) + .file("baz/src/lib.rs", r#" + pub fn gimme() -> String { + "test passed".to_str() + } + "#); + + p.cargo_process("cargo-build") + .exec_with_output() + .assert(); + + assert_that(&p.bin("foo"), existing_file()); + + assert_that( + cargo::util::process(p.bin("foo")), + execs().with_stdout("test passed\n")); +}) + test!(cargo_compile_with_nested_deps_shorthand { let mut p = project("foo"); let bar = p.root().join("bar"); @@ -636,18 +772,16 @@ test!(custom_build_in_dependency { version = "0.5.0" authors = ["wycats@example.com"] build = "{}" - - [[lib]] - name = "bar" "#, escape_path(&build.bin("foo")))) - .file("bar/src/bar.rs", r#" + .file("bar/src/lib.rs", r#" pub fn bar() {} "#); assert_that(p.cargo_process("cargo-build"), execs().with_status(0)); }) -test!(many_crate_types { +// this is testing that src/.rs still works (for now) +test!(many_crate_types_old_style_lib_location { let mut p = project("foo"); p = p .file("Cargo.toml", r#" @@ -685,6 +819,44 @@ test!(many_crate_types { file1.ends_with(os::consts::DLL_SUFFIX)); }) +test!(many_crate_types_correct { + let mut p = project("foo"); + p = p + .file("Cargo.toml", r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [[lib]] + + name = "foo" + crate_type = ["rlib", "dylib"] + "#) + .file("src/lib.rs", r#" + pub fn foo() {} + "#); + assert_that(p.cargo_process("cargo-build"), + execs().with_status(0)); + + let files = fs::readdir(&p.root().join("target")).assert(); + let mut files: Vec = files.iter().filter_map(|f| { + match f.filename_str().unwrap() { + "deps" => None, + s if s.contains("fingerprint") || s.contains("dSYM") => None, + s => Some(s.to_str()) + } + }).collect(); + files.sort(); + let file0 = files.get(0).as_slice(); + let file1 = files.get(1).as_slice(); + println!("{} {}", file0, file1); + assert!(file0.ends_with(".rlib") || file1.ends_with(".rlib")); + assert!(file0.ends_with(os::consts::DLL_SUFFIX) || + file1.ends_with(os::consts::DLL_SUFFIX)); +}) + test!(unused_keys { let mut p = project("foo"); p = p