diff --git a/Makefile.in b/Makefile.in index 716164e4002..3f685a256c6 100644 --- a/Makefile.in +++ b/Makefile.in @@ -102,7 +102,7 @@ clean: # === Documentation -DOCS := index faq config guide manifest native-build pkgid-spec +DOCS := index faq config guide manifest build-script pkgid-spec DOC_DIR := target/doc DOC_OPTS := --markdown-no-toc \ --markdown-css stylesheets/normalize.css \ diff --git a/src/cargo/core/dependency.rs b/src/cargo/core/dependency.rs index fe83cf7160c..eefe72102f6 100644 --- a/src/cargo/core/dependency.rs +++ b/src/cargo/core/dependency.rs @@ -10,7 +10,7 @@ pub struct Dependency { source_id: SourceId, req: VersionReq, specified_req: Option, - transitive: bool, + kind: Kind, only_match_name: bool, optional: bool, @@ -22,6 +22,13 @@ pub struct Dependency { only_for_platform: Option, } +#[deriving(PartialEq, Clone, Show)] +pub enum Kind { + Normal, + Development, + Build, +} + impl Dependency { /// Attempt to create a `Dependency` from an entry in the manifest. /// @@ -55,7 +62,7 @@ impl Dependency { name: name.to_string(), source_id: source_id.clone(), req: VersionReq::any(), - transitive: true, + kind: Normal, only_match_name: true, optional: false, features: Vec::new(), @@ -83,8 +90,8 @@ impl Dependency { &self.source_id } - pub fn transitive(mut self, transitive: bool) -> Dependency { - self.transitive = transitive; + pub fn kind(mut self, kind: Kind) -> Dependency { + self.kind = kind; self } @@ -132,7 +139,15 @@ impl Dependency { } /// Returns false if the dependency is only used to build the local package. - pub fn is_transitive(&self) -> bool { self.transitive } + pub fn is_transitive(&self) -> bool { + match self.kind { + Normal | Build => true, + Development => false, + } + } + pub fn is_build(&self) -> bool { + match self.kind { Build => true, _ => false } + } pub fn is_optional(&self) -> bool { self.optional } /// Returns true if the default features of the dependency are requested. pub fn uses_default_features(&self) -> bool { self.default_features } diff --git a/src/cargo/core/manifest.rs b/src/cargo/core/manifest.rs index bd95992796c..ca40de4c016 100644 --- a/src/cargo/core/manifest.rs +++ b/src/cargo/core/manifest.rs @@ -16,7 +16,8 @@ pub struct Manifest { targets: Vec, target_dir: Path, doc_dir: Path, - build: Vec, + build: Vec, // TODO: deprecated, remove + links: Option, warnings: Vec, exclude: Vec, metadata: ManifestMetadata, @@ -59,7 +60,7 @@ pub struct SerializedManifest { targets: Vec, target_dir: String, doc_dir: String, - build: Option>, + build: Option>, // TODO: deprecated, remove } impl> Encodable for Manifest { @@ -73,6 +74,7 @@ impl> Encodable for Manifest { targets: self.targets.clone(), target_dir: self.target_dir.display().to_string(), doc_dir: self.doc_dir.display().to_string(), + // TODO: deprecated, remove build: if self.build.len() == 0 { None } else { Some(self.build.clone()) }, }.encode(s) } @@ -131,8 +133,9 @@ pub struct Profile { doctest: bool, doc: bool, dest: Option, - plugin: bool, + for_host: bool, harness: bool, // whether to use the test harness (--test) + custom_build: bool, } impl Profile { @@ -146,8 +149,9 @@ impl Profile { test: false, doc: false, dest: None, - plugin: false, + for_host: false, doctest: false, + custom_build: false, harness: true, } } @@ -219,8 +223,13 @@ impl Profile { self.doctest } - pub fn is_plugin(&self) -> bool { - self.plugin + pub fn is_custom_build(&self) -> bool { + self.custom_build + } + + /// Returns true if the target must be built for the host instead of the target. + pub fn is_for_host(&self) -> bool { + self.for_host } pub fn get_opt_level(&self) -> uint { @@ -282,8 +291,9 @@ impl Profile { self } - pub fn plugin(mut self, plugin: bool) -> Profile { - self.plugin = plugin; + /// Sets whether the `Target` must be compiled for the host instead of the target platform. + pub fn for_host(mut self, for_host: bool) -> Profile { + self.for_host = for_host; self } @@ -291,6 +301,12 @@ impl Profile { self.harness = harness; self } + + /// Sets whether the `Target` is a custom build script. + pub fn custom_build(mut self, custom_build: bool) -> Profile { + self.custom_build = custom_build; + self + } } impl hash::Hash for Profile { @@ -302,7 +318,7 @@ impl hash::Hash for Profile { codegen_units, debug, rpath, - plugin, + for_host, ref dest, harness, @@ -313,8 +329,10 @@ impl hash::Hash for Profile { env: _, test: _, doctest: _, + + custom_build: _, } = *self; - (opt_level, codegen_units, debug, rpath, plugin, dest, harness).hash(into) + (opt_level, codegen_units, debug, rpath, for_host, dest, harness).hash(into) } } @@ -366,16 +384,17 @@ impl Show for Target { impl Manifest { pub fn new(summary: Summary, targets: Vec, target_dir: Path, doc_dir: Path, - build: Vec, exclude: Vec, + build: Vec, exclude: Vec, links: Option, metadata: ManifestMetadata) -> Manifest { Manifest { summary: summary, targets: targets, target_dir: target_dir, doc_dir: doc_dir, - build: build, + build: build, // TODO: deprecated, remove warnings: Vec::new(), exclude: exclude, + links: links, metadata: metadata, } } @@ -416,6 +435,10 @@ impl Manifest { self.build.as_slice() } + pub fn get_links(&self) -> Option<&str> { + self.links.as_ref().map(|s| s.as_slice()) + } + pub fn add_warning(&mut self, s: String) { self.warnings.push(s) } @@ -466,6 +489,18 @@ impl Target { } } + /// Builds a `Target` corresponding to the `build = "build.rs"` entry. + pub fn custom_build_target(name: &str, src_path: &Path, profile: &Profile, + metadata: Option) -> Target { + Target { + kind: BinTarget, + name: name.to_string(), + src_path: src_path.clone(), + profile: profile.clone(), + metadata: metadata, + } + } + pub fn example_target(name: &str, src_path: &Path, profile: &Profile) -> Target { Target { kind: ExampleTarget, diff --git a/src/cargo/ops/cargo_clean.rs b/src/cargo/ops/cargo_clean.rs index 0560225a381..7b3aa0ae6df 100644 --- a/src/cargo/ops/cargo_clean.rs +++ b/src/cargo/ops/cargo_clean.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::io::fs::{mod, PathExtensions}; use core::{MultiShell, PackageSet}; @@ -49,7 +50,7 @@ pub fn clean(manifest_path: &Path, opts: &mut CleanOptions) -> CargoResult<()> { let pkgs = PackageSet::new([]); let cx = try!(Context::new("compile", &resolve, &srcs, &pkgs, &mut cfg, Layout::at(root.get_absolute_target_dir()), - None, &pkg)); + None, &pkg, HashMap::new())); // And finally, clean everything out! for target in pkg.get_targets().iter() { diff --git a/src/cargo/ops/cargo_compile.rs b/src/cargo/ops/cargo_compile.rs index 2947efa4b56..6f47fe380b9 100644 --- a/src/cargo/ops/cargo_compile.rs +++ b/src/cargo/ops/cargo_compile.rs @@ -28,7 +28,7 @@ use std::collections::HashMap; use core::registry::PackageRegistry; use core::{MultiShell, Source, SourceId, PackageSet, Package, Target, PackageId}; use core::resolver; -use ops; +use ops::{mod, BuildOutput}; use sources::{PathSource}; use util::config::{Config, ConfigValue}; use util::{CargoResult, Wrap, config, internal, human, ChainError, profile}; @@ -129,7 +129,7 @@ pub fn compile_pkg(package: &Package, options: &mut CompileOptions) }; let targets = to_build.get_targets().iter().filter(|target| { - match env { + target.get_profile().is_custom_build() || match env { // doc-all == document everything, so look for doc targets "doc" | "doc-all" => target.get_profile().get_env() == "doc", env => target.get_profile().get_env() == env, @@ -138,12 +138,12 @@ pub fn compile_pkg(package: &Package, options: &mut CompileOptions) let ret = { let _p = profile::start("compiling"); - try!(scrape_target_config(&config, &user_configs)); + let lib_overrides = try!(scrape_target_config(&config, &user_configs)); try!(ops::compile_targets(env.as_slice(), targets.as_slice(), to_build, &PackageSet::new(packages.as_slice()), &resolve_with_overrides, &sources, - &config)) + &config, lib_overrides)) }; return Ok(ret); @@ -175,41 +175,68 @@ fn source_ids_from_config(configs: &HashMap, fn scrape_target_config(config: &Config, configs: &HashMap) - -> CargoResult<()> { + -> CargoResult> { let target = match configs.find_equiv("target") { - None => return Ok(()), + None => return Ok(HashMap::new()), Some(target) => try!(target.table().chain_error(|| { internal("invalid configuration for the key `target`") })), }; - let target = match config.target() { - None => target, - Some(triple) => match target.find_equiv(triple) { - None => return Ok(()), - Some(target) => try!(target.table().chain_error(|| { - internal(format!("invalid configuration for the key \ - `target.{}`", triple)) - })), - }, + let triple = config.target().unwrap_or(config.rustc_host()).to_string(); + let target = match target.find(&triple) { + None => return Ok(HashMap::new()), + Some(target) => try!(target.table().chain_error(|| { + internal(format!("invalid configuration for the key \ + `target.{}`", triple)) + })), }; - match target.find_equiv("ar") { - None => {} - Some(ar) => { - config.set_ar(try!(ar.string().chain_error(|| { - internal("invalid configuration for key `ar`") - })).ref0().to_string()); - } - } - - match target.find_equiv("linker") { - None => {} - Some(linker) => { - config.set_linker(try!(linker.string().chain_error(|| { - internal("invalid configuration for key `ar`") - })).ref0().to_string()); + let mut ret = HashMap::new(); + for (k, v) in target.iter() { + match k.as_slice() { + "ar" | "linker" => { + let v = try!(v.string().chain_error(|| { + internal(format!("invalid configuration for key `{}`", k)) + })).ref0().to_string(); + if k.as_slice() == "linker" { + config.set_linker(v); + } else { + config.set_ar(v); + } + } + lib_name => { + let table = try!(v.table().chain_error(|| { + internal(format!("invalid configuration for the key \ + `target.{}.{}`", triple, lib_name)) + })); + let mut output = BuildOutput { + library_paths: Vec::new(), + library_links: Vec::new(), + metadata: Vec::new(), + }; + for (k, v) in table.iter() { + let v = try!(v.string().chain_error(|| { + internal(format!("invalid configuration for the key \ + `target.{}.{}.{}`", triple, lib_name, + k)) + })).val0(); + if k.as_slice() == "rustc-flags" { + let whence = format!("in `target.{}.{}.rustc-flags`", + triple, lib_name); + let whence = whence.as_slice(); + let (paths, links) = try!( + BuildOutput::parse_rustc_flags(v.as_slice(), whence) + ); + output.library_paths.extend(paths.into_iter()); + output.library_links.extend(links.into_iter()); + } else { + output.metadata.push((k.to_string(), v.to_string())); + } + } + ret.insert(lib_name.to_string(), output); + } } } - Ok(()) + Ok(ret) } diff --git a/src/cargo/ops/cargo_run.rs b/src/cargo/ops/cargo_run.rs index b464c684f22..39b3e369a89 100644 --- a/src/cargo/ops/cargo_run.rs +++ b/src/cargo/ops/cargo_run.rs @@ -22,7 +22,8 @@ pub fn run(manifest_path: &Path, LibTarget(_) => false, }; let matches_name = name.as_ref().map_or(true, |n| n.as_slice() == a.get_name()); - matches_kind && matches_name && a.get_profile().get_env() == env + matches_kind && matches_name && a.get_profile().get_env() == env && + !a.get_profile().is_custom_build() }); let bin = try!(bins.next().require(|| { human("a bin target must be available for `cargo run`") diff --git a/src/cargo/ops/cargo_rustc/compilation.rs b/src/cargo/ops/cargo_rustc/compilation.rs index 646c79a384b..381cf968847 100644 --- a/src/cargo/ops/cargo_rustc/compilation.rs +++ b/src/cargo/ops/cargo_rustc/compilation.rs @@ -23,6 +23,7 @@ pub struct Compilation { /// /// This is currently used to drive some entries which are added to the /// LD_LIBRARY_PATH as appropriate. + // TODO: deprecated, remove pub native_dirs: HashMap, /// Root output directory (for the local package's artifacts) @@ -43,7 +44,7 @@ impl Compilation { pub fn new(pkg: &Package) -> Compilation { Compilation { libraries: HashMap::new(), - native_dirs: HashMap::new(), + native_dirs: HashMap::new(), // TODO: deprecated, remove root_output: Path::new("/"), deps_output: Path::new("/"), tests: Vec::new(), diff --git a/src/cargo/ops/cargo_rustc/context.rs b/src/cargo/ops/cargo_rustc/context.rs index 0ea5dc8f8fc..b2bd59eb2e1 100644 --- a/src/cargo/ops/cargo_rustc/context.rs +++ b/src/cargo/ops/cargo_rustc/context.rs @@ -1,13 +1,15 @@ use std::collections::HashSet; use std::collections::hash_map::{HashMap, Occupied, Vacant}; use std::str; +use std::sync::Arc; use core::{SourceMap, Package, PackageId, PackageSet, Resolve, Target}; use util::{mod, CargoResult, ChainError, internal, Config, profile}; use util::human; -use super::{Kind, KindPlugin, KindTarget, Compilation}; +use super::{Kind, KindHost, KindTarget, Compilation, BuildOutput}; use super::layout::{Layout, LayoutProxy}; +use super::custom_build::BuildState; #[deriving(Show)] pub enum PlatformRequirement { @@ -21,6 +23,7 @@ pub struct Context<'a, 'b: 'a> { pub resolve: &'a Resolve, pub sources: &'a SourceMap<'b>, pub compilation: Compilation, + pub build_state: Arc, env: &'a str, host: Layout, @@ -37,7 +40,8 @@ impl<'a, 'b: 'a> Context<'a, 'b> { pub fn new(env: &'a str, resolve: &'a Resolve, sources: &'a SourceMap<'b>, deps: &'a PackageSet, config: &'b Config<'b>, host: Layout, target: Option, - root_pkg: &Package) + root_pkg: &Package, + build_state: HashMap) -> CargoResult> { let (target_dylib, target_exe) = try!(Context::filename_parts(config.target())); @@ -63,6 +67,7 @@ impl<'a, 'b: 'a> Context<'a, 'b> { host_dylib: host_dylib, requirements: HashMap::new(), compilation: Compilation::new(root_pkg), + build_state: Arc::new(BuildState::new(build_state, deps)), }) } @@ -145,13 +150,13 @@ impl<'a, 'b: 'a> Context<'a, 'b> { if !visiting.insert(pkg.get_package_id()) { return } let key = (pkg.get_package_id(), target.get_name()); - let req = if target.get_profile().is_plugin() {PlatformPlugin} else {req}; + let req = if target.get_profile().is_for_host() {PlatformPlugin} else {req}; match self.requirements.entry(key) { Occupied(mut entry) => { *entry.get_mut() = entry.get().combine(req); } Vacant(entry) => { entry.set(req); } }; - for &(pkg, dep) in self.dep_targets(pkg).iter() { + for &(pkg, dep) in self.dep_targets(pkg, target).iter() { self.build_requirements(pkg, dep, req, visiting); } @@ -168,10 +173,10 @@ impl<'a, 'b: 'a> Context<'a, 'b> { pub fn layout(&self, pkg: &Package, kind: Kind) -> LayoutProxy { let primary = pkg.get_package_id() == self.resolve.root(); match kind { - KindPlugin => LayoutProxy::new(&self.host, primary), + KindHost => LayoutProxy::new(&self.host, primary), KindTarget => LayoutProxy::new(self.target.as_ref() .unwrap_or(&self.host), - primary) + primary), } } @@ -180,7 +185,7 @@ impl<'a, 'b: 'a> Context<'a, 'b> { /// If `plugin` is true, the pair corresponds to the host platform, /// otherwise it corresponds to the target platform. fn dylib(&self, kind: Kind) -> CargoResult<(&str, &str)> { - let (triple, pair) = if kind == KindPlugin { + let (triple, pair) = if kind == KindHost { (self.config.rustc_host(), &self.host_dylib) } else { (self.target_triple.as_slice(), &self.target_dylib) @@ -207,8 +212,8 @@ impl<'a, 'b: 'a> Context<'a, 'b> { ret.push(format!("{}{}", stem, self.target_exe)); } else { if target.is_dylib() { - let plugin = target.get_profile().is_plugin(); - let kind = if plugin {KindPlugin} else {KindTarget}; + let plugin = target.get_profile().is_for_host(); + let kind = if plugin {KindHost} else {KindTarget}; let (prefix, suffix) = try!(self.dylib(kind)); ret.push(format!("{}{}{}", prefix, stem, suffix)); } @@ -225,17 +230,24 @@ impl<'a, 'b: 'a> Context<'a, 'b> { /// For a package, return all targets which are registered as dependencies /// for that package. - pub fn dep_targets(&self, pkg: &Package) -> Vec<(&'a Package, &'a Target)> { + pub fn dep_targets(&self, pkg: &Package, target: &Target) + -> Vec<(&'a Package, &'a Target)> { let deps = match self.resolve.deps(pkg.get_package_id()) { None => return vec!(), Some(deps) => deps, }; - deps.map(|pkg_id| self.get_package(pkg_id)) - .filter_map(|pkg| { + deps.map(|id| self.get_package(id)).filter(|dep| { + // If this target is a build command, then we only want build + // dependencies, otherwise we want everything *other than* build + // dependencies. + let pkg_dep = pkg.get_dependencies().iter().find(|d| { + d.get_name() == dep.get_name() + }).unwrap(); + target.get_profile().is_custom_build() == pkg_dep.is_build() + }).filter_map(|pkg| { pkg.get_targets().iter().find(|&t| self.is_relevant_target(t)) .map(|t| (pkg, t)) - }) - .collect() + }).collect() } /// Gets a package for the given package id. diff --git a/src/cargo/ops/cargo_rustc/custom_build.rs b/src/cargo/ops/cargo_rustc/custom_build.rs new file mode 100644 index 00000000000..c11c64f6bbf --- /dev/null +++ b/src/cargo/ops/cargo_rustc/custom_build.rs @@ -0,0 +1,303 @@ +use std::collections::HashMap; +use std::fmt; +use std::io::fs::PathExtensions; +use std::io::{fs, USER_RWX, File}; +use std::str; +use std::sync::Mutex; + +use core::{Package, Target, PackageId, PackageSet}; +use util::{CargoResult, CargoError, human}; +use util::{internal, ChainError, Require}; + +use super::job::Work; +use super::{fingerprint, process, KindHost, Context}; +use util::Freshness; + +/// Contains the parsed output of a custom build script. +#[deriving(Clone)] +pub struct BuildOutput { + /// Paths to pass to rustc with the `-L` flag + pub library_paths: Vec, + /// Names and link kinds of libraries, suitable for the `-l` flag + pub library_links: Vec, + /// Metadata to pass to the immediate dependencies + pub metadata: Vec<(String, String)>, +} + +pub struct BuildState { + pub outputs: Mutex>, +} + +/// Prepares a `Work` that executes the target as a custom build script. +pub fn prepare(pkg: &Package, target: &Target, cx: &mut Context) + -> CargoResult<(Work, Work, Freshness)> { + let (script_output, old_script_output, build_output, old_build_output) = { + let layout = cx.layout(pkg, KindHost); + (layout.build(pkg), + layout.proxy().old_build(pkg), + layout.build_out(pkg), + layout.proxy().old_build(pkg).join("out")) + }; + + // Building the command to execute + let to_exec = try!(cx.target_filenames(target))[0].clone(); + let to_exec = script_output.join(to_exec); + + // Start preparing the process to execute, starting out with some + // environment variables. + let profile = target.get_profile(); + let mut p = try!(super::process(to_exec, pkg, target, cx)) + .env("OUT_DIR", Some(&build_output)) + .env("CARGO_MANIFEST_DIR", Some(pkg.get_manifest_path() + .dir_path() + .display().to_string())) + .env("NUM_JOBS", Some(cx.config.jobs().to_string())) + .env("TARGET", Some(cx.target_triple())) + .env("DEBUG", Some(profile.get_debug().to_string())) + .env("OPT_LEVEL", Some(profile.get_opt_level().to_string())) + .env("PROFILE", Some(profile.get_env())); + + // Be sure to pass along all enabled features for this package, this is the + // last piece of statically known information that we have. + match cx.resolve.features(pkg.get_package_id()) { + Some(features) => { + for feat in features.iter() { + p = p.env(format!("CARGO_FEATURE_{}", + super::envify(feat.as_slice())).as_slice(), + Some("1")); + } + } + None => {} + } + + // Gather the set of native dependencies that this package has along with + // some other variables to close over. + // + // This information will be used at build-time later on to figure out which + // sorts of variables need to be discovered at that time. + let lib_deps = { + let non_build_target = pkg.get_targets().iter().find(|t| { + !t.get_profile().is_custom_build() + }).unwrap(); + cx.dep_targets(pkg, non_build_target).iter().filter_map(|&(pkg, _)| { + pkg.get_manifest().get_links().map(|links| { + (links.to_string(), pkg.get_package_id().clone()) + }) + }).collect::>() + }; + let pkg_name = pkg.to_string(); + let build_state = cx.build_state.clone(); + let id = pkg.get_package_id().clone(); + let all = (id.clone(), pkg_name.clone(), build_state.clone(), + script_output.clone(), old_build_output.clone(), + build_output.clone()); + + try!(fs::mkdir(&script_output, USER_RWX)); + + // Prepare the unit of "dirty work" which will actually run the custom build + // command. + // + // Note that this has to do some extra work just before running the command + // to determine extra environment variables and such. + let work = proc(desc_tx: Sender) { + // Make sure that OUT_DIR exists. + // + // If we have an old build directory, then just move it into place, + // otherwise create it! + try!(if old_build_output.exists() { + fs::rename(&old_build_output, &build_output) + } else { + fs::mkdir(&build_output, USER_RWX) + }.chain_error(|| { + internal("failed to create script output directory for \ + build command") + })); + + // For all our native lib dependencies, pick up their metadata to pass + // along to this custom build command. + let mut p = p; + { + let build_state = build_state.outputs.lock(); + for &(ref name, ref id) in lib_deps.iter() { + for &(ref key, ref value) in (*build_state)[*id].metadata.iter() { + p = p.env(format!("DEP_{}_{}", + super::envify(name.as_slice()), + super::envify(key.as_slice())).as_slice(), + Some(value.as_slice())); + } + } + } + + // And now finally, run the build command itself! + desc_tx.send_opt(p.to_string()).ok(); + let output = try!(p.exec_with_output().map_err(|mut e| { + e.msg = format!("Failed to run custom build command for `{}`\n{}", + pkg_name, e.msg); + e.concrete().mark_human() + })); + + // After the build command has finished running, we need to be sure to + // remember all of its output so we can later discover precisely what it + // was, even if we don't run the build command again (due to freshness). + // + // This is also the location where we provide feedback into the build + // state informing what variables were discovered via our script as + // well. + let output = try!(str::from_utf8(output.output.as_slice()).require(|| { + human("build script output was not valid utf-8") + })); + let build_output = try!(BuildOutput::parse(output, pkg_name.as_slice())); + build_state.outputs.lock().insert(id, build_output); + + try!(File::create(&script_output.join("output")) + .write_str(output).map_err(|e| { + human(format!("failed to write output of custom build command: {}", + e)) + })); + + Ok(()) + }; + + // Now that we've prepared our work-to-do, we need to prepare the fresh work + // itself to run when we actually end up just discarding what we calculated + // above. + // + // Note that the freshness calculation here is the build_cmd freshness, not + // target specific freshness. This is because we don't actually know what + // the inputs are to this command! + // + // Also note that a fresh build command needs to + let (freshness, dirty, fresh) = + try!(fingerprint::prepare_build_cmd(cx, pkg, Some(target))); + let dirty = proc(tx: Sender) { try!(work(tx.clone())); dirty(tx) }; + let fresh = proc(tx) { + let (id, pkg_name, build_state, script_output, + old_build_output, build_output) = all; + let new_loc = script_output.join("output"); + try!(fs::rename(&old_script_output.join("output"), &new_loc)); + try!(fs::rename(&old_build_output, &build_output)); + let mut f = try!(File::open(&new_loc).map_err(|e| { + human(format!("failed to read cached build command output: {}", e)) + })); + let contents = try!(f.read_to_string()); + let output = try!(BuildOutput::parse(contents.as_slice(), + pkg_name.as_slice())); + build_state.outputs.lock().insert(id, output); + + fresh(tx) + }; + + Ok((dirty, fresh, freshness)) +} + +impl BuildState { + pub fn new(overrides: HashMap, + packages: &PackageSet) -> BuildState { + let mut sources = HashMap::new(); + for package in packages.iter() { + match package.get_manifest().get_links() { + Some(links) => { + sources.insert(links.to_string(), + package.get_package_id().clone()); + } + None => {} + } + } + let mut outputs = HashMap::new(); + for (name, output) in overrides.into_iter() { + outputs.insert(sources[name].clone(), output); + } + BuildState { outputs: Mutex::new(outputs) } + } +} + +impl BuildOutput { + // Parses the output of a script. + // The `pkg_name` is used for error messages. + pub fn parse(input: &str, pkg_name: &str) -> CargoResult { + let mut library_paths = Vec::new(); + let mut library_links = Vec::new(); + let mut metadata = Vec::new(); + let whence = format!("build script of `{}`", pkg_name); + + for line in input.lines() { + let mut iter = line.splitn(1, |c: char| c == ':'); + if iter.next() != Some("cargo") { + // skip this line since it doesn't start with "cargo:" + continue; + } + let data = match iter.next() { + Some(val) => val, + None => continue + }; + + // getting the `key=value` part of the line + let mut iter = data.splitn(1, |c: char| c == '='); + let key = iter.next(); + let value = iter.next(); + let (key, value) = match (key, value) { + (Some(a), Some(b)) => (a, b.trim_right()), + // line started with `cargo:` but didn't match `key=value` + _ => return Err(human(format!("Wrong output in {}: `{}`", + whence, line))) + }; + + if key == "rustc-flags" { + let whence = whence.as_slice(); + let (libs, links) = try!( + BuildOutput::parse_rustc_flags(value, whence) + ); + library_links.extend(links.into_iter()); + library_paths.extend(libs.into_iter()); + } else { + metadata.push((key.to_string(), value.to_string())) + } + } + + Ok(BuildOutput { + library_paths: library_paths, + library_links: library_links, + metadata: metadata, + }) + } + + pub fn parse_rustc_flags(value: &str, whence: &str) + -> CargoResult<(Vec, Vec)> { + // TODO: some arguments (like paths) may contain spaces + let value = value.trim(); + let mut flags_iter = value.words(); + let (mut library_links, mut library_paths) = (Vec::new(), Vec::new()); + loop { + let flag = match flags_iter.next() { + Some(f) => f, + None => break + }; + if flag != "-l" && flag != "-L" { + return Err(human(format!("Only `-l` and `-L` flags are allowed \ + in {}: `{}`", + whence, value))) + } + let value = match flags_iter.next() { + Some(v) => v, + None => return Err(human(format!("Flag in rustc-flags has no value\ + in {}: `{}`", + whence, value))) + }; + match flag { + "-l" => library_links.push(value.to_string()), + "-L" => library_paths.push(Path::new(value)), + + // was already checked above + _ => return Err(human("only -l and -L flags are allowed")) + }; + } + Ok((library_paths, library_links)) + } +} + +impl fmt::Show for BuildOutput { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "BuildOutput {{ paths: [..], libs: {}, metadata: {} }}", + self.library_links, self.metadata) + } +} diff --git a/src/cargo/ops/cargo_rustc/fingerprint.rs b/src/cargo/ops/cargo_rustc/fingerprint.rs index 135cffeee96..e69734ed88c 100644 --- a/src/cargo/ops/cargo_rustc/fingerprint.rs +++ b/src/cargo/ops/cargo_rustc/fingerprint.rs @@ -84,7 +84,9 @@ pub fn prepare_target(cx: &mut Context, pkg: &Package, target: &Target, let (old_root, root) = { let layout = cx.layout(pkg, kind); - if target.is_example() { + if target.get_profile().is_custom_build() { + (layout.old_build(pkg), layout.build(pkg)) + } else if target.is_example() { (layout.old_examples().clone(), layout.examples().clone()) } else { (layout.old_root().clone(), layout.root().clone()) @@ -134,16 +136,16 @@ pub fn prepare_target(cx: &mut Context, pkg: &Package, target: &Target, /// /// The currently implemented solution is option (1), although it is planned to /// migrate to option (2) in the near future. -pub fn prepare_build_cmd(cx: &mut Context, pkg: &Package) - -> CargoResult { +pub fn prepare_build_cmd(cx: &mut Context, pkg: &Package, + target: Option<&Target>) -> CargoResult { let _p = profile::start(format!("fingerprint build cmd: {}", pkg.get_package_id())); // TODO: this should not explicitly pass KindTarget let kind = KindTarget; - if pkg.get_manifest().get_build().len() == 0 { - return Ok((Fresh, proc() Ok(()), proc() Ok(()))) + if pkg.get_manifest().get_build().len() == 0 && target.is_none() { + return Ok((Fresh, proc(_) Ok(()), proc(_) Ok(()))) } let (old, new) = dirs(cx, pkg, kind); let old_loc = old.join("build"); @@ -155,12 +157,16 @@ pub fn prepare_build_cmd(cx: &mut Context, pkg: &Package) let new_fingerprint = mk_fingerprint(cx, &new_fingerprint); let is_fresh = try!(is_fresh(&old_loc, new_fingerprint.as_slice())); - let pairs = vec![(old_loc, new_loc.clone()), - (cx.layout(pkg, kind).old_native(pkg), - cx.layout(pkg, kind).native(pkg))]; + let mut pairs = vec![(old_loc, new_loc.clone())]; - let native_dir = cx.layout(pkg, kind).native(pkg); - cx.compilation.native_dirs.insert(pkg.get_package_id().clone(), native_dir); + // The new custom build command infrastructure handles its own output + // directory as part of freshness. + if target.is_none() { + let native_dir = cx.layout(pkg, kind).native(pkg); + pairs.push((cx.layout(pkg, kind).old_native(pkg), native_dir.clone())); + cx.compilation.native_dirs.insert(pkg.get_package_id().clone(), + native_dir); + } Ok(prepare(is_fresh, new_loc, new_fingerprint, pairs)) } @@ -171,8 +177,8 @@ pub fn prepare_init(cx: &mut Context, pkg: &Package, kind: Kind) let (_, new1) = dirs(cx, pkg, kind); let new2 = new1.clone(); - let work1 = proc() { try!(fs::mkdir(&new1, USER_RWX)); Ok(()) }; - let work2 = proc() { try!(fs::mkdir(&new2, USER_RWX)); Ok(()) }; + let work1 = proc(_) { try!(fs::mkdir(&new1, USER_RWX)); Ok(()) }; + let work2 = proc(_) { try!(fs::mkdir(&new2, USER_RWX)); Ok(()) }; (work1, work2) } @@ -181,12 +187,14 @@ pub fn prepare_init(cx: &mut Context, pkg: &Package, kind: Kind) /// instances to actually perform the necessary work. fn prepare(is_fresh: bool, loc: Path, fingerprint: String, to_copy: Vec<(Path, Path)>) -> Preparation { - let write_fingerprint = proc() { + let write_fingerprint = proc(desc_tx) { + drop(desc_tx); try!(File::create(&loc).write_str(fingerprint.as_slice())); Ok(()) }; - let move_old = proc() { + let move_old = proc(desc_tx) { + drop(desc_tx); for &(ref src, ref dst) in to_copy.iter() { try!(fs::rename(src, dst)); } diff --git a/src/cargo/ops/cargo_rustc/job.rs b/src/cargo/ops/cargo_rustc/job.rs index 34853a26e54..889932a1aea 100644 --- a/src/cargo/ops/cargo_rustc/job.rs +++ b/src/cargo/ops/cargo_rustc/job.rs @@ -1,18 +1,16 @@ -use std::io::IoResult; - -use core::MultiShell; use util::{CargoResult, Fresh, Dirty, Freshness}; -pub struct Job { dirty: Work, fresh: Work, desc: String } +pub struct Job { dirty: Work, fresh: Work } -pub type Work = proc():Send -> CargoResult<()>; +/// Each proc should send its description before starting. +/// It should send either once or close immediatly. +pub type Work = proc(Sender):Send -> CargoResult<()>; impl Job { /// Create a new job representing a unit of work. - pub fn new(dirty: proc():Send -> CargoResult<()>, - fresh: proc():Send -> CargoResult<()>, - desc: String) -> Job { - Job { dirty: dirty, fresh: fresh, desc: desc } + pub fn new(dirty: Work, + fresh: Work) -> Job { + Job { dirty: dirty, fresh: fresh } } /// Create a new job which will run `fresh` if the job is fresh and @@ -20,25 +18,17 @@ impl Job { /// /// Retains the same signature as `new` for compatibility. This job does not /// describe itself to the console. - pub fn noop(_dirty: proc():Send -> CargoResult<()>, - fresh: proc():Send -> CargoResult<()>, - _desc: String) -> Job { - Job { dirty: proc() Ok(()), fresh: fresh, desc: String::new() } + pub fn noop(_dirty: Work, + fresh: Work) -> Job { + Job { dirty: proc(_) Ok(()), fresh: fresh } } /// Consumes this job by running it, returning the result of the /// computation. - pub fn run(self, fresh: Freshness) -> CargoResult<()> { + pub fn run(self, fresh: Freshness, tx: Sender) -> CargoResult<()> { match fresh { - Fresh => (self.fresh)(), - Dirty => (self.dirty)(), - } - } - - pub fn describe(&self, shell: &mut MultiShell) -> IoResult<()> { - if self.desc.len() > 0 { - try!(shell.status("Running", self.desc.as_slice())); + Fresh => (self.fresh)(tx), + Dirty => (self.dirty)(tx), } - Ok(()) } } diff --git a/src/cargo/ops/cargo_rustc/job_queue.rs b/src/cargo/ops/cargo_rustc/job_queue.rs index 5097267d91c..86d8b9982be 100644 --- a/src/cargo/ops/cargo_rustc/job_queue.rs +++ b/src/cargo/ops/cargo_rustc/job_queue.rs @@ -25,6 +25,7 @@ pub struct JobQueue<'a, 'b> { pending: HashMap<(&'a PackageId, TargetStage), PendingBuild>, state: HashMap<&'a PackageId, Freshness>, ignored: HashSet<&'a PackageId>, + printed: HashSet<&'a PackageId>, } /// A helper structure for metadata about the state of a building package. @@ -48,7 +49,8 @@ struct PendingBuild { #[deriving(Hash, PartialEq, Eq, Clone, PartialOrd, Ord, Show)] pub enum TargetStage { StageStart, - StageCustomBuild, + StageBuildCustomBuild, + StageRunCustomBuild, StageLibraries, StageBinaries, StageTests, @@ -71,6 +73,7 @@ impl<'a, 'b> JobQueue<'a, 'b> { pending: HashMap::new(), state: HashMap::new(), ignored: HashSet::new(), + printed: HashSet::new(), } } @@ -160,15 +163,6 @@ impl<'a, 'b> JobQueue<'a, 'b> { let amt = if njobs == 0 {1} else {njobs}; let id = pkg.get_package_id().clone(); - if stage == StageStart && !self.ignored.contains(&pkg.get_package_id()) { - match fresh.combine(self.state[pkg.get_package_id()]) { - Fresh => try!(config.shell().verbose(|c| { - c.status("Fresh", pkg) - })), - Dirty => try!(config.shell().status("Compiling", pkg)) - } - } - // While the jobs are all running, we maintain some metadata about how // many are running, the current state of freshness (of all the combined // jobs), and the stage to pass to finish() later on. @@ -178,16 +172,22 @@ impl<'a, 'b> JobQueue<'a, 'b> { fresh: fresh, }); + let mut total_fresh = fresh.combine(self.state[pkg.get_package_id()]); + let mut running = Vec::new(); for (job, job_freshness) in jobs.into_iter() { let fresh = job_freshness.combine(fresh); + total_fresh = total_fresh.combine(fresh); let my_tx = self.tx.clone(); let id = id.clone(); - if fresh == Dirty { - try!(config.shell().verbose(|shell| job.describe(shell))); - } + let (desc_tx, desc_rx) = channel(); self.pool.execute(proc() { - my_tx.send((id, stage, fresh, job.run(fresh))); + my_tx.send((id, stage, fresh, job.run(fresh, desc_tx))); }); + // only the first message of each job is processed + match desc_rx.recv_opt() { + Ok(msg) => running.push(msg), + Err(..) => {} + } } // If no work was scheduled, make sure that a message is actually send @@ -195,6 +195,33 @@ impl<'a, 'b> JobQueue<'a, 'b> { if njobs == 0 { self.tx.send((id, stage, fresh, Ok(()))); } + + // Print out some nice progress information + // + // This isn't super trivial becuase we don't want to print loads and + // loads of information to the console, but we also want to produce a + // faithful representation of what's happening. This is somewhat nuanced + // as a package can start compiling *very* early on because of custom + // build commands and such. + // + // In general, we try to print "Compiling" for the first nontrivial task + // run for a package, regardless of when that is. We then don't print + // out any more information for a package after we've printed it once. + let print = !self.ignored.contains(&pkg.get_package_id()); + let print = print && !self.printed.contains(&pkg.get_package_id()); + if print && (stage == StageLibraries || + (total_fresh == Dirty && running.len() > 0)) { + self.printed.insert(pkg.get_package_id()); + match total_fresh { + Fresh => try!(config.shell().verbose(|c| { + c.status("Fresh", pkg) + })), + Dirty => try!(config.shell().status("Compiling", pkg)) + } + } + for msg in running.iter() { + try!(config.shell().verbose(|c| c.status("Running", msg))); + } Ok(()) } } @@ -214,35 +241,57 @@ impl<'a> Dependency<(&'a Resolve, &'a PackageSet)> let (id, stage) = *self; let pkg = packages.iter().find(|p| p.get_package_id() == id).unwrap(); let deps = resolve.deps(id).into_iter().flat_map(|a| a) - .filter(|dep| *dep != id); + .filter(|dep| *dep != id) + .map(|dep| { + (dep, pkg.get_dependencies().iter().find(|d| { + d.get_name() == dep.get_name() + }).unwrap()) + }); match stage { - StageStart => { - // Only transitive dependencies are needed to start building a - // package. Non transitive dependencies (dev dependencies) are - // only used to build tests. - deps.filter(|dep| { - let dep = pkg.get_dependencies().iter().find(|d| { - d.get_name() == dep.get_name() - }).unwrap(); - dep.is_transitive() - }).map(|dep| { - (dep, StageLibraries) - }).collect() + StageStart => Vec::new(), + + // Building the build command itself starts off pretty easily,we + // just need to depend on all of the library stages of our own build + // dependencies (making them available to us). + StageBuildCustomBuild => { + let mut base = vec![(id, StageStart)]; + base.extend(deps.filter(|&(_, dep)| dep.is_build()) + .map(|(id, _)| (id, StageLibraries))); + base } - StageCustomBuild => vec![(id, StageStart)], - StageLibraries => vec![(id, StageCustomBuild)], + + // When running a custom build command, we need to be sure that our + // own custom build command is actually built, and then we need to + // wait for all our dependencies to finish their custom build + // commands themselves (as they may provide input to us). + StageRunCustomBuild => { + let mut base = vec![(id, StageBuildCustomBuild)]; + base.extend(deps.filter(|&(_, dep)| dep.is_transitive()) + .map(|(id, _)| (id, StageRunCustomBuild))); + base + } + + // Building a library depends on our own custom build command plus + // all our transitive dependencies. + StageLibraries => { + let mut base = vec![(id, StageRunCustomBuild)]; + base.extend(deps.filter(|&(_, dep)| dep.is_transitive()) + .map(|(id, _)| (id, StageLibraries))); + base + } + + // Binaries only depend on libraries being available. Note that they + // do not depend on dev-dependencies. StageBinaries => vec![(id, StageLibraries)], + + // Tests depend on all non-transitive dependencies + // (dev-dependencies) in addition to the library stage for this + // package. StageTests => { - let mut ret = vec![(id, StageLibraries)]; - ret.extend(deps.filter(|dep| { - let dep = pkg.get_dependencies().iter().find(|d| { - d.get_name() == dep.get_name() - }).unwrap(); - !dep.is_transitive() - }).map(|dep| { - (dep, StageLibraries) - })); - ret + let mut base = vec![(id, StageLibraries)]; + base.extend(deps.filter(|&(_, dep)| !dep.is_transitive()) + .map(|(id, _)| (id, StageLibraries))); + base } } } diff --git a/src/cargo/ops/cargo_rustc/layout.rs b/src/cargo/ops/cargo_rustc/layout.rs index b2fd7bf8822..a1bbb695ebc 100644 --- a/src/cargo/ops/cargo_rustc/layout.rs +++ b/src/cargo/ops/cargo_rustc/layout.rs @@ -16,6 +16,20 @@ //! //! # This is the location at which the output of all custom build //! # commands are rooted +//! build/ +//! +//! # Each package gets its own directory where its build script and +//! # script output are placed +//! $pkg1/ +//! $pkg2/ +//! $pkg3/ +//! +//! # Each directory package has a `out` directory where output +//! # is placed. +//! out/ +//! +//! # This is the location at which the output of all old custom build +//! # commands are rooted //! native/ //! //! # Each package gets its own directory for where its output is @@ -46,6 +60,7 @@ //! //! # Same as the two above old directories //! old-native/ +//! old-build/ //! old-fingerprint/ //! old-examples/ //! ``` @@ -60,12 +75,14 @@ pub struct Layout { root: Path, deps: Path, native: Path, + build: Path, fingerprint: Path, examples: Path, old_deps: Path, old_root: Path, old_native: Path, + old_build: Path, old_fingerprint: Path, old_examples: Path, } @@ -93,11 +110,13 @@ impl Layout { Layout { deps: root.join("deps"), native: root.join("native"), + build: root.join("build"), fingerprint: root.join(".fingerprint"), examples: root.join("examples"), old_deps: root.join("old-deps"), old_root: root.join("old-root"), old_native: root.join("old-native"), + old_build: root.join("old-build"), old_fingerprint: root.join("old-fingerprint"), old_examples: root.join("old-examples"), root: root, @@ -114,6 +133,7 @@ impl Layout { (&self.old_native, &self.native), (&self.old_fingerprint, &self.fingerprint), (&self.old_examples, &self.examples), + (&self.old_build, &self.build), ])); if self.old_root.exists() { @@ -146,6 +166,8 @@ impl Layout { pub fn dest<'a>(&'a self) -> &'a Path { &self.root } pub fn deps<'a>(&'a self) -> &'a Path { &self.deps } pub fn examples<'a>(&'a self) -> &'a Path { &self.examples } + + // TODO: deprecated, remove pub fn native(&self, package: &Package) -> Path { self.native.join(self.pkg_dir(package)) } @@ -153,9 +175,19 @@ impl Layout { self.fingerprint.join(self.pkg_dir(package)) } + pub fn build(&self, package: &Package) -> Path { + self.build.join(self.pkg_dir(package)) + } + + pub fn build_out(&self, package: &Package) -> Path { + self.build(package).join("out") + } + pub fn old_dest<'a>(&'a self) -> &'a Path { &self.old_root } pub fn old_deps<'a>(&'a self) -> &'a Path { &self.old_deps } pub fn old_examples<'a>(&'a self) -> &'a Path { &self.old_examples } + + // TODO: deprecated, remove pub fn old_native(&self, package: &Package) -> Path { self.old_native.join(self.pkg_dir(package)) } @@ -163,6 +195,10 @@ impl Layout { self.old_fingerprint.join(self.pkg_dir(package)) } + pub fn old_build(&self, package: &Package) -> Path { + self.old_build.join(self.pkg_dir(package)) + } + fn pkg_dir(&self, pkg: &Package) -> String { format!("{}-{}", pkg.get_name(), short_hash(pkg.get_package_id())) } @@ -175,6 +211,7 @@ impl Drop for Layout { let _ = fs::rmdir_recursive(&self.old_native); let _ = fs::rmdir_recursive(&self.old_fingerprint); let _ = fs::rmdir_recursive(&self.old_examples); + let _ = fs::rmdir_recursive(&self.old_build); } } @@ -193,17 +230,27 @@ impl<'a> LayoutProxy<'a> { pub fn examples(&self) -> &'a Path { self.root.examples() } + // TODO: deprecated, remove pub fn native(&self, pkg: &Package) -> Path { self.root.native(pkg) } + pub fn build(&self, pkg: &Package) -> Path { self.root.build(pkg) } + + pub fn build_out(&self, pkg: &Package) -> Path { self.root.build_out(pkg) } + pub fn old_root(&self) -> &'a Path { if self.primary {self.root.old_dest()} else {self.root.old_deps()} } pub fn old_examples(&self) -> &'a Path { self.root.old_examples() } + // TODO: deprecated, remove pub fn old_native(&self, pkg: &Package) -> Path { self.root.old_native(pkg) } + pub fn old_build(&self, pkg: &Package) -> Path { + self.root.old_build(pkg) + } + pub fn proxy(&self) -> &'a Layout { self.root } } diff --git a/src/cargo/ops/cargo_rustc/links.rs b/src/cargo/ops/cargo_rustc/links.rs new file mode 100644 index 00000000000..58b99153c14 --- /dev/null +++ b/src/cargo/ops/cargo_rustc/links.rs @@ -0,0 +1,37 @@ +use std::collections::HashMap; + +use core::PackageSet; +use util::{CargoResult, human}; + +// Validate that there are no duplicated native libraries among packages and +// that all packages with `links` also have a build script. +pub fn validate(deps: &PackageSet) -> CargoResult<()> { + let mut map = HashMap::new(); + + for dep in deps.iter() { + let lib = match dep.get_manifest().get_links() { + Some(lib) => lib, + None => continue, + }; + match map.find(&lib) { + Some(previous) => { + return Err(human(format!("native library `{}` is being linked \ + to by more than one package, and \ + can only be linked to by one \ + package\n\n {}\n {}", + lib, previous, dep.get_package_id()))) + } + None => {} + } + if !dep.get_manifest().get_targets().iter().any(|t| { + t.get_profile().is_custom_build() + }) { + return Err(human(format!("package `{}` specifies that it links to \ + `{}` but does not have a custom build \ + script", dep.get_package_id(), lib))) + } + map.insert(lib, dep.get_package_id()); + } + + Ok(()) +} diff --git a/src/cargo/ops/cargo_rustc/mod.rs b/src/cargo/ops/cargo_rustc/mod.rs index 146b9d861b0..647fb0e6f41 100644 --- a/src/cargo/ops/cargo_rustc/mod.rs +++ b/src/cargo/ops/cargo_rustc/mod.rs @@ -1,4 +1,4 @@ -use std::collections::HashSet; +use std::collections::{HashSet, HashMap}; use std::dynamic_lib::DynamicLibrary; use std::io::{fs, USER_RWX}; use std::io::fs::PathExtensions; @@ -16,16 +16,19 @@ pub use self::context::Context; pub use self::context::{PlatformPlugin, PlatformPluginAndTarget}; pub use self::context::{PlatformRequirement, PlatformTarget}; pub use self::layout::{Layout, LayoutProxy}; +pub use self::custom_build::BuildOutput; mod context; mod compilation; +mod custom_build; mod fingerprint; mod job; mod job_queue; mod layout; +mod links; #[deriving(PartialEq, Eq)] -pub enum Kind { KindPlugin, KindTarget } +pub enum Kind { KindHost, KindTarget } /// Run `rustc` to figure out what its current version string is. /// @@ -72,7 +75,8 @@ fn uniq_target_dest<'a>(targets: &[&'a Target]) -> Option<&'a str> { pub fn compile_targets<'a>(env: &str, targets: &[&'a Target], pkg: &'a Package, deps: &PackageSet, resolve: &'a Resolve, sources: &'a SourceMap, - config: &'a Config<'a>) + config: &'a Config<'a>, + lib_overrides: HashMap) -> CargoResult { if targets.is_empty() { return Ok(Compilation::new(pkg)) @@ -80,6 +84,8 @@ pub fn compile_targets<'a>(env: &str, targets: &[&'a Target], pkg: &'a Package, debug!("compile_targets; targets={}; pkg={}; deps={}", targets, pkg, deps); + try!(links::validate(deps)); + let dest = uniq_target_dest(targets); let root = deps.iter().find(|p| p.get_package_id() == resolve.root()).unwrap(); let host_layout = Layout::new(root, None, dest); @@ -88,7 +94,8 @@ pub fn compile_targets<'a>(env: &str, targets: &[&'a Target], pkg: &'a Package, }); let mut cx = try!(Context::new(env, resolve, sources, deps, config, - host_layout, target_layout, pkg)); + host_layout, target_layout, pkg, + lib_overrides)); let mut queue = JobQueue::new(cx.resolve, deps, cx.config); // First ensure that the destination directory exists @@ -107,7 +114,8 @@ pub fn compile_targets<'a>(env: &str, targets: &[&'a Target], pkg: &'a Package, // Only compile lib targets for dependencies let targets = dep.get_targets().iter().filter(|target| { - cx.is_relevant_target(*target) + target.get_profile().is_custom_build() || + cx.is_relevant_target(*target) }).collect::>(); if targets.len() == 0 && dep.get_package_id() != resolve.root() { @@ -146,71 +154,103 @@ fn compile<'a, 'b>(targets: &[&'a Target], pkg: &'a Package, // Prepare the fingerprint directory as the first step of building a package let (target1, target2) = fingerprint::prepare_init(cx, pkg, KindTarget); - let mut init = vec![(Job::new(target1, target2, String::new()), Fresh)]; + let mut init = vec![(Job::new(target1, target2), Fresh)]; if cx.config.target().is_some() { - let (plugin1, plugin2) = fingerprint::prepare_init(cx, pkg, KindPlugin); - init.push((Job::new(plugin1, plugin2, String::new()), Fresh)); + let (plugin1, plugin2) = fingerprint::prepare_init(cx, pkg, KindHost); + init.push((Job::new(plugin1, plugin2), Fresh)); } jobs.enqueue(pkg, jq::StageStart, init); - // First part of the build step of a target is to execute all of the custom - // build commands. - let mut build_cmds = Vec::new(); - for (i, build_cmd) in pkg.get_manifest().get_build().iter().enumerate() { - let work = try!(compile_custom(pkg, build_cmd.as_slice(), cx, i == 0)); - build_cmds.push(work); - } - let (freshness, dirty, fresh) = - try!(fingerprint::prepare_build_cmd(cx, pkg)); - let desc = match build_cmds.len() { - 0 => String::new(), - 1 => pkg.get_manifest().get_build()[0].to_string(), - _ => format!("custom build commands"), - }; - let dirty = proc() { - for cmd in build_cmds.into_iter() { try!(cmd()) } - dirty() - }; - jobs.enqueue(pkg, jq::StageCustomBuild, vec![(job(dirty, fresh, desc), - freshness)]); - // After the custom command has run, execute rustc for all targets of our // package. // // Each target has its own concept of freshness to ensure incremental // rebuilds on the *target* granularity, not the *package* granularity. let (mut libs, mut bins, mut tests) = (Vec::new(), Vec::new(), Vec::new()); + let (mut build_custom, mut run_custom) = (Vec::new(), Vec::new()); for &target in targets.iter() { + if target.get_profile().is_custom_build() { + // Custom build commands that are for libs that are overridden are + // skipped entirely + if pkg.get_manifest().get_links().is_some() && + cx.build_state.outputs.lock().contains_key(pkg.get_package_id()) { + continue + } + let (dirty, fresh, freshness) = + try!(custom_build::prepare(pkg, target, cx)); + run_custom.push((job(dirty, fresh), freshness)); + } + let work = if target.get_profile().is_doc() { - let (rustdoc, desc) = try!(rustdoc(pkg, target, cx)); - vec![(rustdoc, KindTarget, desc)] + let rustdoc = try!(rustdoc(pkg, target, cx)); + vec![(rustdoc, KindTarget)] } else { let req = cx.get_requirement(pkg, target); try!(rustc(pkg, target, cx, req)) }; - let dst = match (target.is_lib(), target.get_profile().is_test()) { - (_, true) => &mut tests, - (true, _) => &mut libs, - (false, false) if target.get_profile().get_env() == "test" => &mut tests, - (false, false) => &mut bins, + let dst = match (target.is_lib(), + target.get_profile().is_test(), + target.get_profile().is_custom_build()) { + (_, _, true) => &mut build_custom, + (_, true, _) => &mut tests, + (true, _, _) => &mut libs, + (false, false, _) if target.get_profile().get_env() == "test" => &mut tests, + (false, false, _) => &mut bins, }; - for (work, kind, desc) in work.into_iter() { + for (work, kind) in work.into_iter() { let (freshness, dirty, fresh) = try!(fingerprint::prepare_target(cx, pkg, target, kind)); - let dirty = proc() { try!(work()); dirty() }; - dst.push((job(dirty, fresh, desc), freshness)); + let dirty = proc(desc_tx: Sender) { + try!(work(desc_tx.clone())); + dirty(desc_tx) + }; + dst.push((job(dirty, fresh), freshness)); + } + } + + if targets.iter().any(|t| t.get_profile().is_custom_build()) { + // New custom build system + jobs.enqueue(pkg, jq::StageBuildCustomBuild, build_custom); + jobs.enqueue(pkg, jq::StageRunCustomBuild, run_custom); + + } else { + // Old custom build system + // OLD-BUILD: to-remove + let mut build_cmds = Vec::new(); + for (i, build_cmd) in pkg.get_manifest().get_build().iter().enumerate() { + let work = try!(compile_custom_old(pkg, build_cmd.as_slice(), cx, i == 0)); + build_cmds.push(work); } + let (freshness, dirty, fresh) = + try!(fingerprint::prepare_build_cmd(cx, pkg, None)); + let desc = match build_cmds.len() { + 0 => String::new(), + 1 => pkg.get_manifest().get_build()[0].to_string(), + _ => format!("custom build commands"), + }; + let dirty = proc(desc_tx: Sender) { + if desc.len() > 0 { + desc_tx.send_opt(desc).ok(); + } + for cmd in build_cmds.into_iter() { try!(cmd(desc_tx.clone())) } + dirty(desc_tx) + }; + jobs.enqueue(pkg, jq::StageBuildCustomBuild, vec![]); + jobs.enqueue(pkg, jq::StageRunCustomBuild, vec![(job(dirty, fresh), + freshness)]); } + jobs.enqueue(pkg, jq::StageLibraries, libs); jobs.enqueue(pkg, jq::StageBinaries, bins); jobs.enqueue(pkg, jq::StageTests, tests); Ok(()) } -fn compile_custom(pkg: &Package, cmd: &str, - cx: &Context, first: bool) -> CargoResult { +// OLD-BUILD: to-remove +fn compile_custom_old(pkg: &Package, cmd: &str, + cx: &Context, first: bool) -> CargoResult { let root = cx.get_package(cx.resolve.root()); let profile = root.get_manifest().get_targets().iter() .find(|target| target.get_profile().get_env() == cx.env()) @@ -219,6 +259,9 @@ fn compile_custom(pkg: &Package, cmd: &str, Some(profile) => profile, None => return Err(internal(format!("no profile for {}", cx.env()))) }; + // Just need a target which isn't a custom build command + let target = &pkg.get_targets()[0]; + assert!(!target.get_profile().is_custom_build()); // TODO: this needs to be smarter about splitting let mut cmd = cmd.split(' '); @@ -227,7 +270,7 @@ fn compile_custom(pkg: &Package, cmd: &str, let layout = cx.layout(pkg, KindTarget); let output = layout.native(pkg); let old_output = layout.proxy().old_native(pkg); - let mut p = try!(process(cmd.next().unwrap(), pkg, cx)) + let mut p = try!(process(cmd.next().unwrap(), pkg, target, cx)) .env("OUT_DIR", Some(&output)) .env("DEPS_DIR", Some(&output)) .env("TARGET", Some(cx.target_triple())) @@ -240,18 +283,16 @@ fn compile_custom(pkg: &Package, cmd: &str, match cx.resolve.features(pkg.get_package_id()) { Some(features) => { for feat in features.iter() { - let feat = feat.as_slice().chars() - .map(|c| c.to_uppercase()) - .map(|c| if c == '-' {'_'} else {c}) - .collect::(); - p = p.env(format!("CARGO_FEATURE_{}", feat).as_slice(), Some("1")); + p = p.env(format!("CARGO_FEATURE_{}", + envify(feat.as_slice())).as_slice(), + Some("1")); } } None => {} } - for &(pkg, _) in cx.dep_targets(pkg).iter() { + for &(pkg, _) in cx.dep_targets(pkg, target).iter() { let name: String = pkg.get_name().chars().map(|c| { match c { '-' => '_', @@ -263,7 +304,8 @@ fn compile_custom(pkg: &Package, cmd: &str, } let pkg = pkg.to_string(); - Ok(proc() { + Ok(proc(desc_tx: Sender) { + desc_tx.send_opt(p.to_string()).ok(); if first { try!(if old_output.exists() { fs::rename(&old_output, &output) @@ -284,57 +326,97 @@ fn compile_custom(pkg: &Package, cmd: &str, fn rustc(package: &Package, target: &Target, cx: &mut Context, req: PlatformRequirement) - -> CargoResult >{ + -> CargoResult >{ let crate_types = target.rustc_crate_types(); let rustcs = try!(prepare_rustc(package, target, crate_types, cx, req)); Ok(rustcs.into_iter().map(|(rustc, kind)| { let name = package.get_name().to_string(); - let desc = rustc.to_string(); let is_path_source = package.get_package_id().get_source_id().is_path(); let show_warnings = package.get_package_id() == cx.resolve.root() || is_path_source; let rustc = if show_warnings {rustc} else {rustc.arg("-Awarnings")}; - (proc() { + // Prepare the native lib state (extra -L and -l flags) + let build_state = cx.build_state.clone(); + let mut native_lib_deps = Vec::new(); + let current_id = package.get_package_id().clone(); + let has_custom_build = package.get_targets().iter().any(|t| { + t.get_profile().is_custom_build() + }); + + // FIXME: traverse build dependencies and add -L and -l for an + // transitive build deps. + if !target.get_profile().is_custom_build() { + each_dep(package, cx, |dep| { + if dep.get_manifest().get_links().is_some() || + (*dep.get_package_id() == current_id && has_custom_build) { + native_lib_deps.push(dep.get_package_id().clone()); + } + }); + } + + (proc(desc_tx: Sender) { + let mut rustc = rustc; + + // Only at runtime have we discovered what the extra -L and -l + // arguments are for native libraries, so we process those here. + { + let build_state = build_state.outputs.lock(); + for id in native_lib_deps.iter() { + let output = &(*build_state)[*id]; + for path in output.library_paths.iter() { + rustc = rustc.arg("-L").arg(path); + } + if *id == current_id { + for name in output.library_links.iter() { + rustc = rustc.arg("-l").arg(name.as_slice()); + } + } + } + } + + desc_tx.send_opt(rustc.to_string()).ok(); try!(rustc.exec().chain_error(|| { human(format!("Could not compile `{}`.", name)) })); + Ok(()) - }, kind, desc) + + }, kind) }).collect()) } fn prepare_rustc(package: &Package, target: &Target, crate_types: Vec<&str>, cx: &Context, req: PlatformRequirement) -> CargoResult> { - let base = try!(process("rustc", package, cx)); + let base = try!(process("rustc", package, target, cx)); let base = build_base_args(cx, base, package, target, crate_types.as_slice()); let target_cmd = build_plugin_args(base.clone(), cx, package, target, KindTarget); - let plugin_cmd = build_plugin_args(base, cx, package, target, KindPlugin); + let plugin_cmd = build_plugin_args(base, cx, package, target, KindHost); let target_cmd = try!(build_deps_args(target_cmd, target, package, cx, KindTarget)); let plugin_cmd = try!(build_deps_args(plugin_cmd, target, package, cx, - KindPlugin)); + KindHost)); Ok(match req { PlatformTarget => vec![(target_cmd, KindTarget)], - PlatformPlugin => vec![(plugin_cmd, KindPlugin)], + PlatformPlugin => vec![(plugin_cmd, KindHost)], PlatformPluginAndTarget if cx.config.target().is_none() => vec![(target_cmd, KindTarget)], PlatformPluginAndTarget => vec![(target_cmd, KindTarget), - (plugin_cmd, KindPlugin)], + (plugin_cmd, KindHost)], }) } fn rustdoc(package: &Package, target: &Target, - cx: &mut Context) -> CargoResult<(Work, String)> { + cx: &mut Context) -> CargoResult { let kind = KindTarget; let pkg_root = package.get_root(); let cx_root = cx.layout(package, kind).proxy().dest().join("doc"); - let rustdoc = try!(process("rustdoc", package, cx)).cwd(pkg_root.clone()); + let rustdoc = try!(process("rustdoc", package, target, cx)).cwd(pkg_root.clone()); let mut rustdoc = rustdoc.arg(target.get_src_path()) .arg("-o").arg(cx_root) .arg("--crate-name").arg(target.get_name()); @@ -355,7 +437,8 @@ fn rustdoc(package: &Package, target: &Target, let primary = package.get_package_id() == cx.resolve.root(); let name = package.get_name().to_string(); let desc = rustdoc.to_string(); - Ok((proc() { + Ok(proc(desc_tx: Sender) { + desc_tx.send(desc); if primary { try!(rustdoc.exec().chain_error(|| { human(format!("Could not document `{}`.", name)) @@ -374,7 +457,7 @@ fn rustdoc(package: &Package, target: &Target, })) } Ok(()) - }, desc)) + }) } fn build_base_args(cx: &Context, @@ -405,7 +488,7 @@ fn build_base_args(cx: &Context, .rpath(root_profile.get_rpath()) } - if profile.is_plugin() { + if profile.is_for_host() { cmd = cmd.arg("-C").arg("prefer-dynamic"); } @@ -455,12 +538,17 @@ fn build_base_args(cx: &Context, fn build_plugin_args(mut cmd: ProcessBuilder, cx: &Context, pkg: &Package, target: &Target, kind: Kind) -> ProcessBuilder { - cmd = cmd.arg("--out-dir"); - if target.is_example() { - cmd = cmd.arg(cx.layout(pkg, kind).examples()); + let out_dir = cx.layout(pkg, kind); + let out_dir = if target.get_profile().is_custom_build() { + out_dir.build(pkg) + } else if target.is_example() { + out_dir.examples().clone() } else { - cmd = cmd.arg(cx.layout(pkg, kind).root()); - } + out_dir.root().clone() + }; + + cmd = cmd.arg("--out-dir"); + cmd = cmd.arg(out_dir); let (_, dep_info_loc) = fingerprint::dep_info_loc(cx, pkg, target, kind); cmd = cmd.arg("--dep-info").arg(dep_info_loc); @@ -494,8 +582,18 @@ fn build_deps_args(mut cmd: ProcessBuilder, target: &Target, package: &Package, cmd = cmd.arg("-L").arg(layout.root()); cmd = cmd.arg("-L").arg(layout.deps()); + let has_build_cmd = package.get_targets().iter().any(|t| { + t.get_profile().is_custom_build() + }); + cmd = cmd.env("OUT_DIR", if has_build_cmd { + Some(layout.build_out(package)) + } else { + None + }); + // Traverse the entire dependency graph looking for -L paths to pass for // native dependencies. + // OLD-BUILD: to-remove let mut dirs = Vec::new(); each_dep(package, cx, |pkg| { if pkg.get_manifest().get_build().len() > 0 { @@ -506,20 +604,16 @@ fn build_deps_args(mut cmd: ProcessBuilder, target: &Target, package: &Package, cmd = cmd.arg("-L").arg(dir); } - for &(pkg, target) in cx.dep_targets(package).iter() { + for &(pkg, target) in cx.dep_targets(package, target).iter() { cmd = try!(link_to(cmd, pkg, target, cx, kind)); } - let mut targets = package.get_targets().iter().filter(|target| { + let targets = package.get_targets().iter().filter(|target| { target.is_lib() && target.get_profile().is_compile() }); - if target.is_bin() { - for target in targets { - if target.is_staticlib() { - continue; - } - + if target.is_bin() && !target.get_profile().is_custom_build() { + for target in targets.filter(|f| !f.is_staticlib()) { cmd = try!(link_to(cmd, package, target, cx, kind)); } } @@ -532,8 +626,8 @@ fn build_deps_args(mut cmd: ProcessBuilder, target: &Target, package: &Package, // plugin, then we want the plugin directory. Otherwise we want the // target directory (hence the || here). let layout = cx.layout(pkg, match kind { - KindPlugin => KindPlugin, - KindTarget if target.get_profile().is_plugin() => KindPlugin, + KindHost => KindHost, + KindTarget if target.get_profile().is_for_host() => KindHost, KindTarget => KindTarget, }); @@ -550,19 +644,20 @@ fn build_deps_args(mut cmd: ProcessBuilder, target: &Target, package: &Package, } } -pub fn process(cmd: T, pkg: &Package, +pub fn process(cmd: T, pkg: &Package, target: &Target, cx: &Context) -> CargoResult { // When invoking a tool, we need the *host* deps directory in the dynamic // library search path for plugins and such which have dynamic dependencies. - let layout = cx.layout(pkg, KindPlugin); + let layout = cx.layout(pkg, KindHost); let mut search_path = DynamicLibrary::search_path(); search_path.push(layout.deps().clone()); + // OLD-BUILD: to-remove // Also be sure to pick up any native build directories required by plugins // or their dependencies let mut native_search_paths = HashSet::new(); - for &(dep, target) in cx.dep_targets(pkg).iter() { - if !target.get_profile().is_plugin() { continue } + for &(dep, target) in cx.dep_targets(pkg, target).iter() { + if !target.get_profile().is_for_host() { continue } each_dep(dep, cx, |dep| { if dep.get_manifest().get_build().len() > 0 { native_search_paths.insert(layout.native(dep)); @@ -598,3 +693,10 @@ fn each_dep<'a>(pkg: &Package, cx: &'a Context, f: |&'a Package|) { } } } + +fn envify(s: &str) -> String { + s.chars() + .map(|c| c.to_uppercase()) + .map(|c| if c == '-' {'_'} else {c}) + .collect() +} diff --git a/src/cargo/ops/mod.rs b/src/cargo/ops/mod.rs index 77f43cac3e5..d65629986d3 100644 --- a/src/cargo/ops/mod.rs +++ b/src/cargo/ops/mod.rs @@ -2,9 +2,10 @@ pub use self::cargo_clean::{clean, CleanOptions}; pub use self::cargo_compile::{compile, compile_pkg, CompileOptions}; pub use self::cargo_read_manifest::{read_manifest,read_package,read_packages}; pub use self::cargo_rustc::{compile_targets, Compilation, Layout, Kind, rustc_version}; -pub use self::cargo_rustc::{KindTarget, KindPlugin, Context, LayoutProxy}; +pub use self::cargo_rustc::{KindTarget, KindHost, Context, LayoutProxy}; pub use self::cargo_rustc::{PlatformRequirement, PlatformTarget}; pub use self::cargo_rustc::{PlatformPlugin, PlatformPluginAndTarget}; +pub use self::cargo_rustc::{BuildOutput}; pub use self::cargo_run::run; pub use self::cargo_new::{new, NewOptions}; pub use self::cargo_doc::{doc, DocOptions}; diff --git a/src/cargo/util/toml.rs b/src/cargo/util/toml.rs index f92d498ce80..f2abb2dd1f4 100644 --- a/src/cargo/util/toml.rs +++ b/src/cargo/util/toml.rs @@ -10,8 +10,9 @@ use semver; use serialize::{Decodable, Decoder}; use core::SourceId; -use core::manifest::{LibKind, Lib, Dylib, Profile, ManifestMetadata}; use core::{Summary, Manifest, Target, Dependency, PackageId}; +use core::dependency::{Build, Development}; +use core::manifest::{LibKind, Lib, Dylib, Profile, ManifestMetadata}; use core::package_id::Metadata; use util::{CargoResult, Require, human, ToUrl, ToSemver}; @@ -210,6 +211,7 @@ pub struct TomlManifest { bench: Option>, dependencies: Option>, dev_dependencies: Option>, + build_dependencies: Option>, features: Option>>, target: Option>, } @@ -251,7 +253,8 @@ pub struct TomlProject { name: String, version: TomlVersion, authors: Vec, - build: Option, + build: Option, // TODO: `String` instead + links: Option, exclude: Option>, // package metadata @@ -264,6 +267,7 @@ pub struct TomlProject { repository: Option, } +// TODO: deprecated, remove #[deriving(Decodable)] pub enum TomlBuildCommandsList { SingleBuildCommand(String), @@ -440,10 +444,24 @@ impl TomlManifest { self.bench.as_ref().unwrap().iter().map(|t| t.clone()).collect() }; + // processing the custom build script + let (new_build, old_build) = match project.build { + Some(SingleBuildCommand(ref cmd)) => { + if cmd.as_slice().ends_with(".rs") && layout.root.join(cmd.as_slice()).exists() { + (Some(Path::new(cmd.as_slice())), Vec::new()) + } else { + (None, vec!(cmd.clone())) + } + }, + Some(MultipleBuildCommands(ref cmd)) => (None, cmd.clone()), + None => (None, Vec::new()) + }; + // Get targets let profiles = self.profile.clone().unwrap_or(Default::default()); let targets = normalize(lib.as_slice(), bins.as_slice(), + new_build, examples.as_slice(), tests.as_slice(), benches.as_slice(), @@ -465,24 +483,28 @@ impl TomlManifest { }; // Collect the deps - try!(process_dependencies(&mut cx, false, None, self.dependencies.as_ref())); - try!(process_dependencies(&mut cx, true, None, self.dev_dependencies.as_ref())); + try!(process_dependencies(&mut cx, self.dependencies.as_ref(), + |dep| dep)); + try!(process_dependencies(&mut cx, self.dev_dependencies.as_ref(), + |dep| dep.kind(Development))); + try!(process_dependencies(&mut cx, self.build_dependencies.as_ref(), + |dep| dep.kind(Build))); if let Some(targets) = self.target.as_ref() { for (name, platform) in targets.iter() { - try!(process_dependencies(&mut cx, false, Some(name.clone()), - platform.dependencies.as_ref())); + try!(process_dependencies(&mut cx, + platform.dependencies.as_ref(), + |dep| { + dep.only_for_platform(Some(name.clone())) + })); } } } - let build = match project.build { - Some(SingleBuildCommand(ref cmd)) => vec!(cmd.clone()), - Some(MultipleBuildCommands(ref cmd)) => cmd.clone(), - None => Vec::new() - }; let exclude = project.exclude.clone().unwrap_or(Vec::new()); + let has_old_build = old_build.len() >= 1; + let summary = try!(Summary::new(pkgid, deps, self.features.clone() .unwrap_or(HashMap::new()))); @@ -500,19 +522,24 @@ impl TomlManifest { targets, layout.root.join("target"), layout.root.join("doc"), - build, + old_build, exclude, + project.links.clone(), metadata); if used_deprecated_lib { manifest.add_warning(format!("the [[lib]] section has been \ deprecated in favor of [lib]")); } + if has_old_build { + manifest.add_warning(format!("warning: the old build command has been deprecated")); + } Ok((manifest, nested_paths)) } } -fn process_dependencies<'a>(cx: &mut Context<'a>, dev: bool, platform: Option, - new_deps: Option<&HashMap>) +fn process_dependencies<'a>(cx: &mut Context<'a>, + new_deps: Option<&HashMap>, + f: |Dependency| -> Dependency) -> CargoResult<()> { let dependencies = match new_deps { Some(ref dependencies) => dependencies, @@ -551,8 +578,7 @@ fn process_dependencies<'a>(cx: &mut Context<'a>, dev: bool, platform: Option, examples: &[TomlExampleTarget], tests: &[TomlTestTarget], benches: &[TomlBenchTarget], @@ -689,7 +716,7 @@ fn normalize(libs: &[TomlLibTarget], } if target.plugin == Some(true) { - ret = ret.into_iter().map(|p| p.plugin(true)).collect(); + ret = ret.into_iter().map(|p| p.for_host(true)).collect(); } ret @@ -747,6 +774,21 @@ fn normalize(libs: &[TomlLibTarget], } } + fn custom_build_target(dst: &mut Vec, cmd: &Path, + profiles: &TomlProfiles) { + let profiles = [ + merge(Profile::default_dev().for_host(true).custom_build(true), + &profiles.dev), + ]; + + let name = format!("build-script-{}", cmd.filestem_str().unwrap_or("")); + + for profile in profiles.iter() { + dst.push(Target::custom_build_target(name.as_slice(), + cmd, profile, None)); + } + } + fn example_targets(dst: &mut Vec, examples: &[TomlExampleTarget], profiles: &TomlProfiles, default: |&TomlExampleTarget| -> String) { @@ -829,6 +871,9 @@ fn normalize(libs: &[TomlLibTarget], ([], []) => () } + if let Some(custom_build) = custom_build { + custom_build_target(&mut ret, &custom_build, profiles); + } example_targets(&mut ret, examples, profiles, |ex| format!("examples/{}.rs", ex.name)); diff --git a/src/doc/build-script.md b/src/doc/build-script.md new file mode 100644 index 00000000000..712608657be --- /dev/null +++ b/src/doc/build-script.md @@ -0,0 +1,514 @@ +% Build Script Support + +Some packages need to compile third-party non-Rust code, for example C +libraries. Other packages need to link to C libraries which can either be +located on the system or possibly need to be built from source. Others still +need facilities for functionality such as code generation before building (think +parser generators). + +Cargo does not aim to replace other tools that are well-optimized for +these tasks, but it does integrate with them with the `build` configuration +option. + +```toml +[package] +# ... +build = "build.rs" +``` + +The Rust file designated by the `build` command (relative to the package root) +will be compiled and invoked before anything else is compiled in the package, +allowing your Rust code to depend on the built or generated artifacts. Note +that there is no default value for `build`, it must be explicitly specified if +required. + +Some example use cases of the build command are: + +* Building a bundled C library. +* Finding a C library on the host system. +* Generating a Rust module from a specification. +* Performing any platform-specific configuration neeeded for the crate. + +Each of these use cases will be detailed in full below to give examples of how +the build command works. + +## Inputs to the Build Script + +When the build script is run, there are a number of inputs to the build script, +all passed in the form of environment variables: + +* `OUT_DIR` - the folder in which all output should be placed. This folder is + inside the build directory for the package being built, and it is + unique for the package in question. +* `TARGET` - the target triple that is being compiled for. Native code should be + compiled for this triple. Some more information about target + triples can be found in [clang's own documentation][clang]. +* `NUM_JOBS` - the parallelism specified as the top-level parallelism. This can + be useful to pass a `-j` parameter to a system like `make`. +* `CARGO_MANIFEST_DIR` - The directory containing the manifest for the package + being built (the package containing the build + script). Also note that this is the value of the + current working directory of the build script when it + starts. +* `OPT_LEVEL`, `DEBUG` - values of the corresponding variables for the + profile currently being built. +* `PROFILE` - name of the profile currently being built (see + [profiles][profile]). +* `CARGO_FEATURE_` - For each activated feature of the package being + built, this environment variable will be present + where `` is the name of the feature uppercased + and having `-` translated to `_`. +* `DEP__` - For more information about this set of environment + variables, see the section below about [`links`][links]. + +In addition to the above environment variables, the build script's current +directory is the source directory of the build script's package. + +[profile]: manifest.html#the-[profile.*]-sections +[links]: #the-links-manifest-key +[clang]:http://clang.llvm.org/docs/CrossCompilation.html#target-triple + +## Outputs of the Build Script + +All the lines printed to stdout by a build script that start with `cargo:` +are interpreted by Cargo and must be of the form `key=value`. + +Example output: + +``` +cargo:rustc-flags=-l static:foo -L /path/to/foo +cargo:root=/path/to/foo +cargo:libdir=/path/to/foo/lib +cargo:include=/path/to/foo/include +``` + +The `rustc-flags` key is special and indicates the flags that Cargo will +pass to Rustc. Currently only `-l` and `-L` are accepted. + +Any other element is a user-defined metadata that will be passed to +dependencies. More information about this can be found in the [`links`][links] +section. + +## Build Dependencies + +Build scripts are also allowed to have dependencies on other Cargo-based crates. +Dependencies are declared through the `build-dependencies` section of the +manifest. + +```toml +[build-dependencies.foo] +git = "https://github.com/your-packages/foo" +``` + +The build script **does not** have access to the dependencies listed in the +`dependencies` or `dev-dependencies` section (they're not built yet!). All build +dependencies will also not be available to the package itself unless explicitly +stated as so. + +## The `links` Manifest Key + +In addition to the manifest key `build`, Cargo also supports a `links` manifest +key to declare the name of a native library that is being linked to: + +```toml +[package] +# ... +links = "foo" +build = "build.rs" +``` + +This manifest states that the packages links to the `libfoo` native library, and +it also has a build script for locating and/or building the library. Cargo +requires that a `build` command is specified if a `links` entry is also +specified. + +The purpose of this manifest key is to give Cargo an understanding about the set +of native dependencies that a package has, as well as providing a principled +system of passing metadata between package build scripts. + +Primarily, Cargo requires that there is at most one package per `links` value. +In other words, it's forbidden to have two packages link to the same native +library. Note, however, that there are [conventions in place][star-sys] to +alleviate this. + +[star-sys]: #*-sys-packages + +As mentioned above in the output format, each build script can generate an +arbitrary set of metadata in the form of key-value pairs. This metadata is +passed to the build scripts of **dependent** packages. For example, if `libbar` +depends on `libfoo`, then if `libfoo` generates `key=value` as part of its +metadata, then the build script of `libbar` will have the environment variables +`DEP_FOO_KEY=value`. + +Note that metadata is only passed to immediate dependents, not transitive +dependents. The motivation for this metadata passing is outlined in the linking +to system libraries case study below. + +## Overriding Build Scripts + +If a manifest contains a `links` key, then Cargo supports overriding the build +script specified with a custom library. The purpose of this functionality is to +prevent running the build script in question altogether and instead supply the +metadata ahead of time. + +To override a build script, place the following configuration in any acceptable +Cargo [configuration location][config.html]. + +```toml +[target.x86_64-unknown-linux-gnu.foo] +rustc-flags = "-L /path/to/foo -l foo" +root = "/path/to/foo" +key = "value" +``` + +This section states that for the target `x86_64-unknown-linux-gnu` the library +named `foo` has the metadata specified. This metadata is the same as the +metadata generated as if the build script had run, providing a number of +key/value pairs where the `rustc-flags` key is slightly special. + +With this configuration, if a package declares that it links to `foo` then the +build script will **not** be compiled or run, and the metadata specified will +instead be used. + +# Case study: Code generation + +Some Cargo packages need to have code generated just before they are compiled +for various reasons. Here we'll walk through a simple example which generates a +library call as part of the build script. + +First, let's take a look at the directory structure of this package: + +```notrust +. +├── Cargo.toml +├── build.rs +└── src + └── main.rs + +1 directory, 3 files +``` + +Here we can see that we have a `build.rs` build script and our binary in +`main.rs`. Next, let's take a look at the manifest: + +```toml +# Cargo.toml + +[package] + +name = "hello-from-generated-code" +version = "0.0.1" +authors = ["you@example.com"] +build = "build.rs" +``` + +Here we can se we've got a build script specified which we'll use to generate +some code. Let's see what's inside the build script: + +``` +// build.rs + +use std::os; +use std::io::File; + +fn main() { + let dst = Path::new(os::getenv("OUT_DIR").unwrap()); + let mut f = File::create(&dst.join("hello.rs")).unwrap(); + f.write_str(" + pub fn message() -> &'static str { + \"Hello, World!\" + } + ").unwrap(); +} +``` + +There's a couple of points of note here: + +* The script uses the `OUT_DIR` environment variable to discover where the ouput + files should be located. It can use the process's current working directory to + find where the input files should be located, but in this case we don't have + any input files. +* This script is relatively simple as it just writes out a small generated file. + One could imagine that other more fanciful operations could take place such as + generating a Rust module from a C header file or another language definition, + for example. + +Next, let's peek at the library itself: + +``` +// src/main.rs + +include!(concat!(env!("OUT_DIR"), "/hello.rs")) + +fn main() { + println!("{}", message()); +} +``` + +This is where the real magic happens. The library is using the rustc-defined +`include!` macro in combination with the `concat!` and `env!` macros to include +the generated file (`mod.rs`) into the crate's compilation. + +Using the structure shown here, crates can include any number of generated files +from the build script itself. We've also seen a brief example of how a build +script can use a crate as a dependency purely for the build process and not for +the crate itself at runtime. + +# Case study: Building some native code + +Sometimes it's necessary to build some native C or C++ code as part of a +package. This is another excellent use case of leveraging the build script to +build a native library before the Rust crate itself. As an example, we'll create +a Rust library which calls into C to print "Hello, World!". + +Like above, let's first take a look at the project layout: + +```notrust +. +├── Cargo.toml +├── build.rs +└── src + ├── hello.c + └── main.rs + +1 directory, 4 files +``` + +Pretty similar to before! Next, the manifest: + +```toml +# Cargo.toml + +[package] + +name = "hello-world-from-c" +version = "0.0.1" +authors = [ "you@example.com" ] +build = "build.rs" +``` + +For now we're not going to use any build dependencies, so let's take a look at +the build script now: + +```rust +// build.rs + +use std::io::Command; +use std::os; + +fn main() { + let out_dir = os::getenv("OUT_DIR").unwrap(); + + // note that there are a number of downsides to this approach, the comments + // below detail how to improve the portability of these commands. + Command::new("gcc").arg("src/hello.c") + .arg("-c") + .arg("-o") + .arg(format!("{}/hello.o", out_dir)) + .status() + .unwrap(); + Command::new("ar").arg("crus") + .arg("libhello.a") + .arg("hello.o") + .cwd(&out_dir) + .status() + .unwrap(); + + println!("cargo:rustc-flags=-L {} -l hello:static", out_dir); +} +``` + +This build script starts out by compiling out C file into an object file (by +invoking `gcc`) and then converting this object file into a static library (by +invoking `ar`). The final step is feedback to Cargo itself to say that our +output was in `out_dir` and the compiler should link the crate to `libhello.a` +statically via the `-l hello:static` flag. + +Note that there are a number of drawbacks to this hardcoded approach: + +* The `gcc` command itself is not portable across platforms. For example it's + unlikely that Windows platforms have `gcc`, and not even all Unix platforms + may have `gcc`. The `ar` command is also in a similar situation. +* These commands do not take cross-compilation into account. If we're cross + compiling for a platform such as Android it's unlikely that `gcc` will produce + an ARM executable. + +Not to fear, though, this is where a `build-dependencies` entry would help! The +Cargo ecosystem has a number of packages to make this sort of task much easier, +portable, and standardized. For example, the build script could be written as: + +```rust +// build.rs + +// Bring in a dependency on an externally maintained `cc` package which manages +// invoking the C compiler. +extern crate cc; + +fn main() { + cc::compile_library("libhello.a", &["src/hello.c"]).unwrap(); +} +``` + +This example is a little hand-wavy, but we can assume that the `cc` crate +performs tasks such as: + +* It invokes the appropriate compiler (MSVC for windows, `gcc` for MinGW, `cc` + for Unix platforms, etc). +* It takes the `TARGET` variable into account by passing appropriate flags to + the compiler being used. +* Other environment variables, such as `OPT_LEVEL`, `DEBUG`, etc, are all + handled automatically. +* The stdout output and `OUT_DIR` locations are also handled by the `gcc` + library. + +here we can start to see some of the major benefits of farming as much +functionality as possible out to common build dependencies rather than +duplicating logic across all build scripts! + +Back to the case study though, let's take a quick look at the contents of the +`src` directory: + +```c +// src/hello.c + +#include + +void hello() { + printf("Hello, World!\n"); +} +``` + +```rust +// src/main.rs + +// Note the lack of the `#[link]` attribute. We're delegating the responsibility +// of selecting what to link to over to the build script rather than hardcoding +// it in the source file. +extern { fn hello(); } + +fn main() { + unsafe { hello(); } +} +``` + +And there we go! This should complete our example of building some C code from a +Cargo package using the build script itself. This also shows why using a build +dependency can be crucial in many situations and even much more concise! + +# Case study: Linking to system libraries + +The final case study here will be investigating how a Cargo library links to a +system library and how the build script is leveraged to support this use case. + +Quite frequently a Rust crate wants to link to a native library often provided +on the system to bind its functionality or just use it as part of an +implementation detail. This is quite a nuanced problem when it comes to +performing this in a platform-agnostic fashion, and the purpose of a build +script is again to farm out as much of this as possible to make this as easy as +possible for consumers. + +As an example to follow, let's take a look at one of [Cargo's own +dependencies][git2-rs], [libgit2][libgit2]. This library has a number of +constraints: + +[git2-rs]: https://github.com/alexcrichton/git2-rs/tree/master/libgit2-sys +[libgit2]: https://github.com/libgit2/libgit2 + +* It has an optional dependency on OpenSSL on Unix to implement the https + transport. +* It has an optional dependency on libssh2 on all platforms to implement the ssh + transport. +* It is often not installed on all systems by default. +* It can be built from source using `cmake`. + +To visualize what's going on here, let's take a look at the manifest for the +relevant Cargo package. + +```toml +[package] +name = "libgit2-sys" +version = "0.0.1" +authors = ["..."] +links = "git2" +build = "build.rs" + +[dependencies.libssh2-sys] +git = "https://github.com/alexcrichton/ssh2-rs" + +[target.x86_64-unknown-linux-gnu.dependencies.openssl-sys] +git = "https://github.com/alexcrichton/openssl-sys" + +# ... +``` + +As the above manifests show, we've got a `build` script specified, but it's +worth noting that this example has a `links` entry which indicates that the +crate (`libgit2-sys`) links to the `git2` native library. + +Here we also see the unconditional dependency on `libssh2` via the +`libssh2-sys` crate, as well as a platform-specific dependency on `openssl-sys` +for unix (other variants elided for now). It may seem a little counterintuitive +to express *C dependencies* in the *Cargo manifest*, but this is actually using +one of Cargo's conventions in this space. + +## `*-sys` Packages + +To alleviate linking to system libraries, Cargo has a *convention* of package +naming and functionality. Any package named `foo-sys` will provide two major +pieces of functionality: + +* The library crate will link to the native library `libfoo`. This will often + probe the current system for `libfoo` before resorting to building from + source. +* The library crate will provide **declarations** for functions in `libfoo`, + but it does **not** provide bindings or higher-level abstractions. + +The set of `*-sys` packages provides a common set of dependencies for linking +to native libraries. There are a number of benefits earned from having this +convention of native-library-related packages: + +* Common dependencies on `foo-sys` alleviates the above rule about one package + per value of `links`. +* A common dependency allows centralizing logic on discovering `libfoo` itself + (or building it from source). +* These dependencies are easily overridable. + +## Building libgit2 + +Now that we've got libgit2's dependencies sorted out, we need to actually write +the build script. We're not going to look at specific snippets of code here and +instead only take a look at the high-level details of the build script of +`libgit2-sys`. This is not recommending all packages follow this strategy, but +rather just outlining one specific strategy. + +The first step of the build script should do is to query whether libgit2 is +already installed on the host system. To do this we'll leverage the preexisting +tool `pkg-config` (when its available). We'll also use a `build-dependencies` +section to refactor out all the `pkg-config` related code (or someone's already +done that!). + +If `pkg-config` failed to find libgit2, or if `pkg-config` just wasn't +installed, the next step is to build libgit2 from bundled source code +(distributed as part of `libgit2-sys` itself). There are a few nuances when +doing so that we need to take into account, however: + +* The build system of libgit2, `cmake`, needs to be able to find libgit2's + optional dependency of libssh2. We're sure we've already built it (it's a + Cargo dependency), we just need to communicate this information. To do this + we leverage the metadata format to communicate information between build + scripts. In this example the libssh2 package printed out `cargo:root=...` to + tell us where libssh2 is installed at, and we can then pass this along to + cmake with the `CMAKE_PREFIX_PATH` environment variable. + +* We'll need to handle some `CFLAGS` values when compiling C code (and tell + `cmake` about this). Some flags we may want to pass are `-m64` for 64-bit + code, `-m32` for 32-bit code, or `-fPIC` for 64-bit code as well. + +* Finally, we'll invoke `cmake` to place all output into the `OUT_DIR` + environment variable, and then we'll print the necessary metadata to instruct + rustc how to link to libgit2. + +Most of the functionality of this build script is easily refactorable into +common dependencies, so our build script isn't quite as intimidating as this +descriptions! In reality it's expected that build scripts are quite succinct by +farming logic such as above to build dependencies. diff --git a/src/doc/footer.html b/src/doc/footer.html index 040a6ee032c..0d648dc81e5 100644 --- a/src/doc/footer.html +++ b/src/doc/footer.html @@ -2,7 +2,7 @@ Guide | Frequently Asked Questions | Manifest Format | - Building Non-Rust Code | + Build Scripts | Configuration diff --git a/src/doc/manifest.md b/src/doc/manifest.md index 20566886293..0acfa476ad0 100644 --- a/src/doc/manifest.md +++ b/src/doc/manifest.md @@ -32,7 +32,7 @@ your Rust code, for example. More information can be found in the building non-rust code [guide][2] [1]: http://doc.rust-lang.org/rust.html#external-blocks -[2]: native-build.html +[2]: build-script.html ```toml [package] @@ -341,7 +341,7 @@ the `target` directory. # Examples Files located under `examples` are example uses of the functionality -provided by the library. When compiled, they are placed in the +provided by the library. When compiled, they are placed in the `target/examples` directory. They must compile as executables (with `main.rs`) and load in the diff --git a/src/doc/native-build.md b/src/doc/native-build.md deleted file mode 100644 index da6dcf56457..00000000000 --- a/src/doc/native-build.md +++ /dev/null @@ -1,145 +0,0 @@ -% Building external code - -Some packages need to compile third-party non-Rust code that you will -link into your Rust code using `#[link]` (more information on `#[link]` -can be found in [the Rust manual][1]). - -Cargo does not aim to replace other tools that are well-optimized for -building C or C++ code, but it does integrate with them with the `build` -configuration option. - -```toml -[package] - -name = "hello-world-from-c" -version = "0.0.1" -authors = [ "you@example.com" ] -build = "make" -``` - -The `build` command will be invoked before `rustc`, allowing your Rust -code to depend on the built artifacts. - -Here's what you need to know: - -* Cargo passes your build script an environment variable named - `OUT_DIR`, which is where you should put any compiled artifacts. It - will be different for different Cargo commands, but Cargo will always - pass that output directory as a lib directory to `rustc`. -* Cargo will retain all output in `OUT_DIR` for clean packages across - builds (intelligently discarding the compiled artifacts for dirty - dependencies). Do not put the output of a build command in any other - directory. -* The actual location of `$OUT_DIR` is - `/path/to/project/target/native/$your-out-dir`. -* The target triple that the build command should compile for is specified by - the `TARGET` environment variable. - -What this means is that the normal workflow for build dependencies is: - -* The first time a user types `cargo build` for a project that contains - your package, your `build` script will be invoked. Place any artifacts - into the provided `$OUT_DIR`. -* The next time a user runs `cargo build`, if the dependency has not - changed (via `cargo update `), Cargo will reuse the - output you provided before. Your build command will not be invoked. -* If the user updates your package to a new version (or git revision), - Cargo will **not** remove the old `$OUT_DIR` will re-invoke your build script. - Your build script is responsible for bringing the state of the old directory - up to date with the current state of the input files. - -In general, build scripts may not be as portable as we'd like today. We -encourage package authors to write build scripts that can work in both -Windows and Unix environments. - -Several people who work on Cargo are also working on a project called -[link-config][2], which is a Rust syntax extension whose goal is to -enable portable external compilation and linkage against system -packages. We intend for it to eventually serve this purpose for Cargo -projects. - -[1]: http://doc.rust-lang.org/rust.html#linkage -[2]: https://github.com/alexcrichton/link-config - -# Environment Variables - -The following environment variables are always available for build -commands. - -* `OUT_DIR` - the folder in which all output should be placed. -* `TARGET` - the target triple that is being compiled for. Native code should be - compiled for this triple. -* `NUM_JOBS` - the parallelism specified as the top-level parallelism. This can - be useful to pass a `-j` parameter to a system like `make`. -* `DEP__OUT_DIR` - This variable is present for all immediate dependencies - of the package being built. The `` will be the - package's name, in uppercase, with `-` characters - translated to a `_`. The value of this variable is the - directory in which all the output of the dependency's - build command was placed. This is useful for picking up - things like header files and such from other packages. -* `CARGO_MANIFEST_DIR` - The directory containing the manifest for the package - being built. -* `OPT_LEVEL`, `DEBUG` - values of the corresponding variables for the - profile currently being built. -* `PROFILE` - name of the profile currently being built (see - [profiles][profile]). -* `CARGO_FEATURE_` - For each activated feature of the package being - built, this environment variable will be present - where `` is the name of the feature uppercased - and having `-` translated to `_`. - -[profile]: manifest.html#the-[profile.*]-sections - -# A complete example - -The code blocks below lay out a cargo project which has a small and simple C -dependency along with the necessary infrastructure for linking that to the rust -program. - -```toml -# Cargo.toml -[package] - -name = "hello-world-from-c" -version = "0.0.1" -authors = [ "you@example.com" ] -build = "make -C build" -``` - -```make -# build/Makefile - -# Support cross compilation to/from 32/64 bit. -ARCH := $(word 1, $(subst -, ,$(TARGET))) -ifeq ($(ARCH),i686) -CFLAGS += -m32 -fPIC -else -CFLAGS += -m64 -fPIC -endif - -all: - $(CC) $(CFLAGS) hello.c -c -o "$$OUT_DIR"/hello.o - $(AR) crus "$$OUT_DIR"/libhello.a "$$OUT_DIR"/hello.o - -``` - -```c -// build/hello.c -int foo() { return 1; } -``` - -```rust -// src/main.rs -extern crate libc; - -#[link(name = "hello", kind = "static")] -extern { - fn foo() -> libc::c_int; -} - -fn main() { - let number = unsafe { foo() }; - println!("found {} from C!", number); -} -``` diff --git a/tests/resolve.rs b/tests/resolve.rs index 44fae3425be..1c9b270961a 100644 --- a/tests/resolve.rs +++ b/tests/resolve.rs @@ -8,6 +8,7 @@ use std::collections::HashMap; use hamcrest::{assert_that, equal_to, contains}; use cargo::core::source::SourceId; +use cargo::core::dependency::Development; use cargo::core::{Dependency, PackageId, Summary, Registry}; use cargo::util::{CargoResult, ToUrl}; use cargo::core::resolver::{mod, ResolveEverything}; @@ -191,14 +192,14 @@ fn test_resolving_with_same_name() { #[test] fn test_resolving_with_dev_deps() { let mut reg = registry(vec!( - pkg!("foo" => ["bar", dep("baz").transitive(false)]), - pkg!("baz" => ["bat", dep("bam").transitive(false)]), + pkg!("foo" => ["bar", dep("baz").kind(Development)]), + pkg!("baz" => ["bat", dep("bam").kind(Development)]), pkg!("bar"), pkg!("bat") )); let res = resolve(pkg_id("root"), - vec![dep("foo"), dep("baz").transitive(false)], + vec![dep("foo"), dep("baz").kind(Development)], &mut reg).unwrap(); assert_that(&res, contains(names(["root", "foo", "bar", "baz"]))); diff --git a/tests/support/mod.rs b/tests/support/mod.rs index 544e9800df2..ff61bb06f19 100644 --- a/tests/support/mod.rs +++ b/tests/support/mod.rs @@ -347,13 +347,13 @@ impl Execs { fn lines_match(expected: &str, mut actual: &str) -> bool { for part in expected.split_str("[..]") { match actual.find_str(part) { - Some(i) => actual = actual.slice_from(i), + Some(i) => actual = actual.slice_from(i + part.len()), None => { return false } } } - return true; + actual.len() == 0 || expected.ends_with("[..]") } struct ZipAll { diff --git a/tests/test_cargo_bench.rs b/tests/test_cargo_bench.rs index 5fec4ec0761..67a72b19511 100644 --- a/tests/test_cargo_bench.rs +++ b/tests/test_cargo_bench.rs @@ -38,7 +38,7 @@ test!(cargo_bench_simple { assert_that(p.process(cargo_dir().join("cargo")).arg("bench"), execs().with_stdout(format!("\ {} foo v0.5.0 ({}) -{} target[..]release[..]foo +{} target[..]release[..]foo-[..] running 1 test test bench_hello ... bench: 0 ns/iter (+/- 0) @@ -175,7 +175,7 @@ test!(cargo_bench_failing_test { assert_that(p.process(cargo_dir().join("cargo")).arg("bench"), execs().with_stdout(format!("\ {} foo v0.5.0 ({}) -{} target[..]release[..]foo +{} target[..]release[..]foo-[..] running 1 test test bench_hello ... ", @@ -233,7 +233,7 @@ test bin_bench ... bench: 0 ns/iter (+/- 0) test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured -{running} target[..]release[..]foo +{running} target[..]release[..]foo-[..] running 1 test test lib_bench ... bench: 0 ns/iter (+/- 0) @@ -426,7 +426,7 @@ test!(pass_through_command_line { execs().with_status(0) .with_stdout(format!("\ {compiling} foo v0.0.1 ({dir}) -{running} target[..]release[..]foo +{running} target[..]release[..]foo-[..] running 1 test test bar ... bench: 0 ns/iter (+/- 0) @@ -441,7 +441,7 @@ test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured execs().with_status(0) .with_stdout(format!("\ {compiling} foo v0.0.1 ({dir}) -{running} target[..]release[..]foo +{running} target[..]release[..]foo-[..] running 1 test test foo ... bench: 0 ns/iter (+/- 0) diff --git a/tests/test_cargo_compile.rs b/tests/test_cargo_compile.rs index 219de6acffe..7b40945142c 100644 --- a/tests/test_cargo_compile.rs +++ b/tests/test_cargo_compile.rs @@ -216,7 +216,7 @@ test!(cargo_compile_with_warnings_in_a_dep_package { .with_stderr("\ [..]warning: function is never used: `dead`[..] [..]fn dead() {} - +[..]^~~~~~~~~~~~ ")); assert_that(&p.bin("foo"), existing_file()); @@ -509,307 +509,6 @@ version required: * // test!(compiling_project_with_invalid_manifest) -test!(custom_build { - let mut build = project("builder"); - build = build - .file("Cargo.toml", r#" - [project] - - name = "foo" - version = "0.5.0" - authors = ["wycats@example.com"] - - [[bin]] name = "foo" - "#) - .file("src/foo.rs", r#" - fn main() { println!("Hello!"); } - "#); - assert_that(build.cargo_process("build"), - execs().with_status(0)); - - - let mut p = project("foo"); - p = p - .file("Cargo.toml", format!(r#" - [project] - - name = "foo" - version = "0.5.0" - authors = ["wycats@example.com"] - build = '{}' - - [[bin]] name = "foo" - "#, build.bin("foo").display())) - .file("src/foo.rs", r#" - fn main() {} - "#); - assert_that(p.cargo_process("build"), - execs().with_status(0) - .with_stdout(format!(" Compiling foo v0.5.0 ({})\n", - p.url())) - .with_stderr("")); -}) - -test!(custom_multiple_build { - let mut build1 = project("builder1"); - build1 = build1 - .file("Cargo.toml", r#" - [project] - - name = "foo" - version = "0.5.0" - authors = ["wycats@example.com"] - - [[bin]] name = "foo" - "#) - .file("src/foo.rs", r#" - fn main() { - let args = ::std::os::args(); - assert_eq!(args[1], "hello".to_string()); - assert_eq!(args[2], "world".to_string()); - } - "#); - assert_that(build1.cargo_process("build"), - execs().with_status(0)); - - let mut build2 = project("builder2"); - build2 = build2 - .file("Cargo.toml", r#" - [project] - - name = "bar" - version = "0.5.0" - authors = ["wycats@example.com"] - - [[bin]] name = "bar" - "#) - .file("src/bar.rs", r#" - fn main() { - let args = ::std::os::args(); - assert_eq!(args[1], "cargo".to_string()); - } - "#); - assert_that(build2.cargo_process("build"), - execs().with_status(0)); - - let mut p = project("foo"); - p = p - .file("Cargo.toml", format!(r#" - [project] - - name = "foo" - version = "0.5.0" - authors = ["wycats@example.com"] - build = [ '{} hello world', '{} cargo' ] - - [[bin]] name = "foo" - "#, build1.bin("foo").display(), build2.bin("bar").display())) - .file("src/foo.rs", r#" - fn main() {} - "#); - assert_that(p.cargo_process("build"), - execs().with_status(0) - .with_stdout(format!(" Compiling foo v0.5.0 ({})\n", - p.url())) - .with_stderr("")); -}) - -test!(custom_build_failure { - let mut build = project("builder"); - build = build - .file("Cargo.toml", r#" - [project] - - name = "foo" - version = "0.5.0" - authors = ["wycats@example.com"] - - [[bin]] - name = "foo" - "#) - .file("src/foo.rs", r#" - fn main() { panic!("nope") } - "#); - assert_that(build.cargo_process("build"), execs().with_status(0)); - - - let mut p = project("foo"); - p = p - .file("Cargo.toml", format!(r#" - [project] - - name = "foo" - version = "0.5.0" - authors = ["wycats@example.com"] - build = '{}' - - [[bin]] - name = "foo" - "#, build.bin("foo").display())) - .file("src/foo.rs", r#" - fn main() {} - "#); - assert_that(p.cargo_process("build"), - execs().with_status(101).with_stderr(format!("\ -Failed to run custom build command for `foo v0.5.0 ({dir}) -Process didn't exit successfully: `{}` (status=101)\n\ ---- stderr\n\ -task '
' panicked at 'nope', {filename}:2\n\ -\n\ -", build.bin("foo").display(), filename = format!("src{}foo.rs", path::SEP), - dir = p.url()))); -}) - -test!(custom_second_build_failure { - let mut build1 = project("builder1"); - build1 = build1 - .file("Cargo.toml", r#" - [project] - - name = "foo" - version = "0.5.0" - authors = ["wycats@example.com"] - - [[bin]] name = "foo" - "#) - .file("src/foo.rs", r#" - fn main() { println!("Hello!"); } - "#); - assert_that(build1.cargo_process("build"), - execs().with_status(0)); - - let mut build2 = project("builder2"); - build2 = build2 - .file("Cargo.toml", r#" - [project] - - name = "bar" - version = "0.5.0" - authors = ["wycats@example.com"] - - [[bin]] - name = "bar" - "#) - .file("src/bar.rs", r#" - fn main() { panic!("nope") } - "#); - assert_that(build2.cargo_process("build"), execs().with_status(0)); - - - let mut p = project("foo"); - p = p - .file("Cargo.toml", format!(r#" - [project] - - name = "foo" - version = "0.5.0" - authors = ["wycats@example.com"] - build = [ '{}', '{}' ] - - [[bin]] - name = "foo" - "#, build1.bin("foo").display(), build2.bin("bar").display())) - .file("src/foo.rs", r#" - fn main() {} - "#); - assert_that(p.cargo_process("build"), - execs().with_status(101).with_stderr(format!("\ -Failed to run custom build command for `foo v0.5.0 ({dir}) -Process didn't exit successfully: `{}` (status=101)\n\ ---- stderr\n\ -task '
' panicked at 'nope', {filename}:2\n\ -\n\ -", build2.bin("bar").display(), filename = format!("src{}bar.rs", path::SEP), - dir = p.url()))); -}) - -test!(custom_build_env_vars { - let bar = project("bar") - .file("Cargo.toml", r#" - [package] - name = "bar-bar" - version = "0.0.1" - authors = [] - build = "true" - "#) - .file("src/lib.rs", ""); - bar.build(); - - let mut p = project("foo"); - let mut build = project("builder"); - build = build - .file("Cargo.toml", r#" - [project] - - name = "foo" - version = "0.5.0" - authors = ["wycats@example.com"] - - [features] - foo = [] - - [[bin]] - name = "foo" - "#) - .file("src/foo.rs", format!(r#" - use std::os; - use std::io::fs::PathExtensions; - fn main() {{ - let _ncpus = os::getenv("NUM_JOBS").unwrap(); - let _feat = os::getenv("CARGO_FEATURE_FOO").unwrap(); - let debug = os::getenv("DEBUG").unwrap(); - assert_eq!(debug.as_slice(), "true"); - - let opt = os::getenv("OPT_LEVEL").unwrap(); - assert_eq!(opt.as_slice(), "0"); - - let opt = os::getenv("PROFILE").unwrap(); - assert_eq!(opt.as_slice(), "compile"); - - let out = os::getenv("OUT_DIR").unwrap(); - assert!(out.as_slice().starts_with(r"{0}")); - assert!(Path::new(out).is_dir()); - - let out = os::getenv("DEP_BAR_BAR_OUT_DIR").unwrap(); - assert!(out.as_slice().starts_with(r"{0}")); - assert!(Path::new(out).is_dir()); - - let out = os::getenv("CARGO_MANIFEST_DIR").unwrap(); - let p1 = Path::new(out); - let p2 = os::make_absolute(&Path::new(file!()).dir_path().dir_path()); - assert!(p1 == p2, "{{}} != {{}}", p1.display(), p2.display()); - }} - "#, - p.root().join("target").join("native").display())); - assert_that(build.cargo_process("build").arg("--features").arg("foo"), - execs().with_status(0)); - - - p = p - .file("Cargo.toml", format!(r#" - [project] - - name = "foo" - version = "0.5.0" - authors = ["wycats@example.com"] - build = '{}' - - [features] - foo = [] - - [[bin]] - name = "foo" - - [dependencies.bar-bar] - path = '{}' - "#, build.bin("foo").display(), bar.root().display())) - .file("src/foo.rs", r#" - fn main() {} - "#); - assert_that(p.cargo_process("build").arg("--features").arg("foo"), - execs().with_status(0)); -}) - test!(crate_version_env_vars { let p = project("foo") .file("Cargo.toml", r#" @@ -857,99 +556,6 @@ test!(crate_version_env_vars { execs().with_status(0)); }) -test!(custom_build_in_dependency { - let mut p = project("foo"); - let mut build = project("builder"); - build = build - .file("Cargo.toml", r#" - [project] - - name = "foo" - version = "0.5.0" - authors = ["wycats@example.com"] - - [[bin]] - name = "foo" - "#) - .file("src/foo.rs", format!(r#" - use std::os; - fn main() {{ - assert!(os::getenv("OUT_DIR").unwrap().as_slice() - .starts_with(r"{}")); - }} - "#, - p.root().join("target/native/bar-").display())); - assert_that(build.cargo_process("build"), execs().with_status(0)); - - - p = p - .file("Cargo.toml", r#" - [project] - - name = "foo" - version = "0.5.0" - authors = ["wycats@example.com"] - - [[bin]] - name = "foo" - [dependencies.bar] - path = "bar" - "#) - .file("src/foo.rs", r#" - extern crate bar; - fn main() { bar::bar() } - "#) - .file("bar/Cargo.toml", format!(r#" - [project] - - name = "bar" - version = "0.5.0" - authors = ["wycats@example.com"] - build = '{}' - "#, build.bin("foo").display())) - .file("bar/src/lib.rs", r#" - pub fn bar() {} - "#); - assert_that(p.cargo_process("build"), - execs().with_status(0)); -}) - -// tests that custom build in dep can be built twice in a row - issue 227 -test!(custom_build_in_dependency_twice { - let p = project("foo") - .file("Cargo.toml", r#" - [project] - - name = "foo" - version = "0.5.0" - authors = ["wycats@example.com"] - - [[bin]] - name = "foo" - [dependencies.bar] - path = "./bar" - "#) - .file("src/foo.rs", r#" - extern crate bar; - fn main() { bar::bar() } - "#) - .file("bar/Cargo.toml", format!(r#" - [project] - - name = "bar" - version = "0.0.1" - authors = ["wycats@example.com"] - build = '{}' - "#, "echo test")) - .file("bar/src/lib.rs", r#" - pub fn bar() {} - "#); - assert_that(p.cargo_process("build"), - execs().with_status(0)); - assert_that(p.process(cargo_dir().join("cargo")).arg("build"), - execs().with_status(0)); -}) - // this is testing that src/.rs still works (for now) test!(many_crate_types_old_style_lib_location { let mut p = project("foo"); @@ -975,7 +581,7 @@ test!(many_crate_types_old_style_lib_location { let files = fs::readdir(&p.root().join("target")).assert(); let mut files: Vec = files.iter().filter_map(|f| { match f.filename_str().unwrap() { - "examples" | "deps" => None, + "build" | "examples" | "deps" => None, s if s.contains("fingerprint") || s.contains("dSYM") => None, s => Some(s.to_string()) } @@ -1013,7 +619,7 @@ test!(many_crate_types_correct { let files = fs::readdir(&p.root().join("target")).assert(); let mut files: Vec = files.iter().filter_map(|f| { match f.filename_str().unwrap() { - "examples" | "deps" => None, + "build" | "examples" | "deps" => None, s if s.contains("fingerprint") || s.contains("dSYM") => None, s => Some(s.to_string()) } @@ -1584,7 +1190,7 @@ test!(rebuild_preserves_out_dir { build = build .file("Cargo.toml", r#" [package] - name = "build" + name = "builder" version = "0.5.0" authors = ["wycats@example.com"] "#) @@ -1610,7 +1216,7 @@ test!(rebuild_preserves_out_dir { version = "0.0.0" authors = [] build = '{}' - "#, build.bin("build").display()).as_slice()) + "#, build.bin("builder").display()).as_slice()) .file("src/lib.rs", "pub fn bar() -> int { 1 }"); foo.build(); foo.root().move_into_the_past().assert(); diff --git a/tests/test_cargo_compile_custom_build.rs b/tests/test_cargo_compile_custom_build.rs new file mode 100644 index 00000000000..b81f83a77e4 --- /dev/null +++ b/tests/test_cargo_compile_custom_build.rs @@ -0,0 +1,794 @@ +use std::io::File; + +use support::{project, execs, cargo_dir}; +use support::{COMPILING, RUNNING, DOCTEST}; +use support::paths::PathExt; +use hamcrest::{assert_that}; + +fn setup() { +} + +test!(custom_build_script_failed { + let p = project("foo") + .file("Cargo.toml", r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + build = "build.rs" + "#) + .file("src/main.rs", r#" + fn main() {} + "#) + .file("build.rs", r#" + fn main() { + std::os::set_exit_status(101); + } + "#); + assert_that(p.cargo_process("build").arg("-v"), + execs().with_status(101) + .with_stdout(format!("\ +{compiling} foo v0.5.0 ({url}) +{running} `rustc build.rs --crate-name build-script-build --crate-type bin [..]` +{running} `[..]build-script-build[..]` +", +url = p.url(), compiling = COMPILING, running = RUNNING)) + .with_stderr(format!("\ +Failed to run custom build command for `foo v0.5.0 ({})` +Process didn't exit successfully: `[..]build[..]build-script-build[..]` (status=101)", +p.url()))); +}) + +test!(custom_build_env_vars { + let p = project("foo") + .file("Cargo.toml", r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [features] + bar_feat = ["bar/foo"] + + [dependencies.bar] + path = "bar" + "#) + .file("src/main.rs", r#" + fn main() {} + "#) + .file("bar/Cargo.toml", r#" + [project] + + name = "bar" + version = "0.5.0" + authors = ["wycats@example.com"] + build = "build.rs" + + [features] + foo = [] + "#) + .file("bar/src/lib.rs", r#" + pub fn hello() {} + "#); + + let file_content = format!(r#" + use std::os; + use std::io::fs::PathExtensions; + fn main() {{ + let _target = os::getenv("TARGET").unwrap(); + + let _ncpus = os::getenv("NUM_JOBS").unwrap(); + + let out = os::getenv("CARGO_MANIFEST_DIR").unwrap(); + let p1 = Path::new(out); + let p2 = os::make_absolute(&Path::new(file!()).dir_path().dir_path()); + assert!(p1 == p2, "{{}} != {{}}", p1.display(), p2.display()); + + let opt = os::getenv("OPT_LEVEL").unwrap(); + assert_eq!(opt.as_slice(), "0"); + + let opt = os::getenv("PROFILE").unwrap(); + assert_eq!(opt.as_slice(), "compile"); + + let debug = os::getenv("DEBUG").unwrap(); + assert_eq!(debug.as_slice(), "true"); + + let out = os::getenv("OUT_DIR").unwrap(); + assert!(out.as_slice().starts_with(r"{0}")); + assert!(Path::new(out).is_dir()); + + let _feat = os::getenv("CARGO_FEATURE_FOO").unwrap(); + }} + "#, + p.root().join("target").join("build").display()); + + let p = p.file("bar/build.rs", file_content); + + + assert_that(p.cargo_process("build").arg("--features").arg("bar_feat"), + execs().with_status(0)); +}) + +test!(custom_build_script_wrong_rustc_flags { + let p = project("foo") + .file("Cargo.toml", r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + build = "build.rs" + "#) + .file("src/main.rs", r#" + fn main() {} + "#) + .file("build.rs", r#" + fn main() { + println!("cargo:rustc-flags=-aaa -bbb"); + } + "#); + + assert_that(p.cargo_process("build"), + execs().with_status(101) + .with_stderr(format!("\ +Only `-l` and `-L` flags are allowed in build script of `foo v0.5.0 ({})`: \ +`-aaa -bbb`", +p.url()))); +}) + +/* +test!(custom_build_script_rustc_flags { + let p = project("foo") + .file("Cargo.toml", r#" + [project] + + name = "bar" + version = "0.5.0" + authors = ["wycats@example.com"] + + [dependencies.foo] + path = "foo" + "#) + .file("src/main.rs", r#" + fn main() {} + "#) + .file("foo/Cargo.toml", r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + build = "build.rs" + "#) + .file("foo/src/lib.rs", r#" + "#) + .file("foo/build.rs", r#" + fn main() { + println!("cargo:rustc-flags=-l nonexistinglib -L /dummy/path1 -L /dummy/path2"); + } + "#); + + // TODO: TEST FAILS BECAUSE OF WRONG STDOUT (but otherwise, the build works) + assert_that(p.cargo_process("build").arg("--verbose"), + execs().with_status(101) + .with_stdout(format!("\ +{compiling} bar v0.5.0 ({url}) +{running} `rustc {dir}{sep}src{sep}lib.rs --crate-name test --crate-type lib -g \ + -C metadata=[..] \ + -C extra-filename=-[..] \ + --out-dir {dir}{sep}target \ + --dep-info [..] \ + -L {dir}{sep}target \ + -L {dir}{sep}target{sep}deps` +", +running = RUNNING, compiling = COMPILING, sep = path::SEP, +dir = p.root().display(), +url = p.url(), +))); +}) +*/ + +test!(links_no_build_cmd { + let p = project("foo") + .file("Cargo.toml", r#" + [project] + name = "foo" + version = "0.5.0" + authors = [] + links = "a" + "#) + .file("src/lib.rs", ""); + + assert_that(p.cargo_process("build"), + execs().with_status(101) + .with_stderr("\ +package `foo v0.5.0 (file://[..])` specifies that it links to `a` but does \ +not have a custom build script +")); +}) + +test!(links_duplicates { + let p = project("foo") + .file("Cargo.toml", r#" + [project] + name = "foo" + version = "0.5.0" + authors = [] + links = "a" + build = "build.rs" + + [dependencies.a] + path = "a" + "#) + .file("src/lib.rs", "") + .file("build.rs", "") + .file("a/Cargo.toml", r#" + [project] + name = "a" + version = "0.5.0" + authors = [] + links = "a" + build = "build.rs" + "#) + .file("a/src/lib.rs", "") + .file("a/build.rs", ""); + + assert_that(p.cargo_process("build"), + execs().with_status(101) + .with_stderr("\ +native library `a` is being linked to by more than one package, and can only be \ +linked to by one package + + foo v0.5.0 (file://[..]) + a v0.5.0 (file://[..]) +")); +}) + +test!(overrides_and_links { + let (_, target) = ::cargo::ops::rustc_version().unwrap(); + + let p = project("foo") + .file("Cargo.toml", r#" + [project] + name = "foo" + version = "0.5.0" + authors = [] + build = "build.rs" + + [dependencies.a] + path = "a" + "#) + .file("src/lib.rs", "") + .file("build.rs", r#" + use std::os; + fn main() { + assert_eq!(os::getenv("DEP_FOO_FOO").unwrap().as_slice(), "bar"); + assert_eq!(os::getenv("DEP_FOO_BAR").unwrap().as_slice(), "baz"); + } + "#) + .file(".cargo/config", format!(r#" + [target.{}.foo] + rustc-flags = "-L foo -L bar" + foo = "bar" + bar = "baz" + "#, target).as_slice()) + .file("a/Cargo.toml", r#" + [project] + name = "a" + version = "0.5.0" + authors = [] + links = "foo" + build = "build.rs" + "#) + .file("a/src/lib.rs", "") + .file("a/build.rs", "not valid rust code"); + + assert_that(p.cargo_process("build").arg("-v"), + execs().with_status(0) + .with_stdout(format!("\ +{compiling} foo v0.5.0 (file://[..]) +{running} `rustc build.rs [..]` +{compiling} a v0.5.0 (file://[..]) +{running} `rustc [..] --crate-name a [..]` +{running} `[..]build-script-build[..]` +{running} `rustc [..] --crate-name foo [..] -L foo -L bar[..]` +", compiling = COMPILING, running = RUNNING).as_slice())); +}) + +test!(links_passes_env_vars { + let p = project("foo") + .file("Cargo.toml", r#" + [project] + name = "foo" + version = "0.5.0" + authors = [] + build = "build.rs" + + [dependencies.a] + path = "a" + "#) + .file("src/lib.rs", "") + .file("build.rs", r#" + use std::os; + fn main() { + assert_eq!(os::getenv("DEP_FOO_FOO").unwrap().as_slice(), "bar"); + assert_eq!(os::getenv("DEP_FOO_BAR").unwrap().as_slice(), "baz"); + } + "#) + .file("a/Cargo.toml", r#" + [project] + name = "a" + version = "0.5.0" + authors = [] + links = "foo" + build = "build.rs" + "#) + .file("a/src/lib.rs", "") + .file("a/build.rs", r#" + fn main() { + println!("cargo:foo=bar"); + println!("cargo:bar=baz"); + } + "#); + + assert_that(p.cargo_process("build").arg("-v"), + execs().with_status(0) + .with_stdout(format!("\ +{compiling} [..] v0.5.0 (file://[..]) +{running} `rustc build.rs [..]` +{compiling} [..] v0.5.0 (file://[..]) +{running} `rustc build.rs [..]` +{running} `[..]` +{running} `[..]` +{running} `[..]` +{running} `rustc [..] --crate-name foo [..]` +", compiling = COMPILING, running = RUNNING).as_slice())); +}) + +test!(only_rerun_build_script { + let p = project("foo") + .file("Cargo.toml", r#" + [project] + name = "foo" + version = "0.5.0" + authors = [] + build = "build.rs" + "#) + .file("src/lib.rs", "") + .file("build.rs", r#" + fn main() {} + "#); + + assert_that(p.cargo_process("build").arg("-v"), + execs().with_status(0)); + p.root().move_into_the_past().unwrap(); + + File::create(&p.root().join("some-new-file")).unwrap(); + + assert_that(p.process(cargo_dir().join("cargo")).arg("build").arg("-v"), + execs().with_status(0) + .with_stdout(format!("\ +{compiling} foo v0.5.0 (file://[..]) +{running} `[..]build-script-build[..]` +{running} `rustc [..] --crate-name foo [..]` +", compiling = COMPILING, running = RUNNING).as_slice())); +}) + +test!(rebuild_continues_to_pass_env_vars { + let a = project("a") + .file("Cargo.toml", r#" + [project] + name = "a" + version = "0.5.0" + authors = [] + links = "foo" + build = "build.rs" + "#) + .file("src/lib.rs", "") + .file("build.rs", r#" + fn main() { + println!("cargo:foo=bar"); + println!("cargo:bar=baz"); + } + "#); + a.build(); + a.root().move_into_the_past().unwrap(); + + let p = project("foo") + .file("Cargo.toml", format!(r#" + [project] + name = "foo" + version = "0.5.0" + authors = [] + build = "build.rs" + + [dependencies.a] + path = '{}' + "#, a.root().display())) + .file("src/lib.rs", "") + .file("build.rs", r#" + use std::os; + fn main() { + assert_eq!(os::getenv("DEP_FOO_FOO").unwrap().as_slice(), "bar"); + assert_eq!(os::getenv("DEP_FOO_BAR").unwrap().as_slice(), "baz"); + } + "#); + + assert_that(p.cargo_process("build").arg("-v"), + execs().with_status(0)); + p.root().move_into_the_past().unwrap(); + + File::create(&p.root().join("some-new-file")).unwrap(); + + assert_that(p.process(cargo_dir().join("cargo")).arg("build").arg("-v"), + execs().with_status(0)); +}) + +test!(testing_and_such { + let p = project("foo") + .file("Cargo.toml", r#" + [project] + name = "foo" + version = "0.5.0" + authors = [] + build = "build.rs" + "#) + .file("src/lib.rs", "") + .file("build.rs", r#" + fn main() {} + "#); + + assert_that(p.cargo_process("build").arg("-v"), + execs().with_status(0)); + p.root().move_into_the_past().unwrap(); + + File::create(&p.root().join("src/lib.rs")).unwrap(); + + assert_that(p.process(cargo_dir().join("cargo")).arg("test").arg("-vj1"), + execs().with_status(0) + .with_stdout(format!("\ +{compiling} foo v0.5.0 (file://[..]) +{running} `[..]build-script-build[..]` +{running} `rustc [..] --crate-name foo [..]` +{running} `rustc [..] --test [..]` +{running} `[..]foo-[..]` + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured + +{doctest} foo +{running} `rustdoc --test [..]` + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured + +", compiling = COMPILING, running = RUNNING, doctest = DOCTEST).as_slice())); + + assert_that(p.process(cargo_dir().join("cargo")).arg("doc").arg("-v"), + execs().with_status(0) + .with_stdout(format!("\ +{compiling} foo v0.5.0 (file://[..]) +{running} `rustdoc [..]` +{running} `rustc [..]` +", compiling = COMPILING, running = RUNNING).as_slice())); + + File::create(&p.root().join("src/main.rs")).write_str("fn main() {}").unwrap(); + assert_that(p.process(cargo_dir().join("cargo")).arg("run"), + execs().with_status(0) + .with_stdout(format!("\ +{compiling} foo v0.5.0 (file://[..]) +{running} `target[..]foo` +", compiling = COMPILING, running = RUNNING).as_slice())); +}) + +test!(propagation_of_l_flags { + let (_, target) = ::cargo::ops::rustc_version().unwrap(); + let p = project("foo") + .file("Cargo.toml", r#" + [project] + name = "foo" + version = "0.5.0" + authors = [] + [dependencies.a] + path = "a" + "#) + .file("src/lib.rs", "") + .file("a/Cargo.toml", r#" + [project] + name = "a" + version = "0.5.0" + authors = [] + links = "bar" + build = "build.rs" + + [dependencies.b] + path = "../b" + "#) + .file("a/src/lib.rs", "") + .file("a/build.rs", r#" + fn main() { + println!("cargo:rustc-flags=-L bar"); + } + "#) + .file("b/Cargo.toml", r#" + [project] + name = "b" + version = "0.5.0" + authors = [] + links = "foo" + build = "build.rs" + "#) + .file("b/src/lib.rs", "") + .file("b/build.rs", "bad file") + .file(".cargo/config", format!(r#" + [target.{}.foo] + rustc-flags = "-L foo" + "#, target).as_slice()); + + assert_that(p.cargo_process("build").arg("-v").arg("-j1"), + execs().with_status(0) + .with_stdout(format!("\ +{compiling} a v0.5.0 (file://[..]) +{running} `rustc build.rs [..]` +{compiling} b v0.5.0 (file://[..]) +{running} `rustc [..] --crate-name b [..]-L foo[..]` +{running} `[..]a-[..]build-script-build[..]` +{running} `rustc [..] --crate-name a [..]-L bar[..]-L foo[..]` +{compiling} foo v0.5.0 (file://[..]) +{running} `rustc [..] --crate-name foo [..] -L bar[..]-L foo[..]` +", compiling = COMPILING, running = RUNNING).as_slice())); +}) + +test!(build_deps_simple { + let p = project("foo") + .file("Cargo.toml", r#" + [project] + name = "foo" + version = "0.5.0" + authors = [] + build = "build.rs" + [build-dependencies.a] + path = "a" + "#) + .file("src/lib.rs", "") + .file("build.rs", " + extern crate a; + fn main() {} + ") + .file("a/Cargo.toml", r#" + [project] + name = "a" + version = "0.5.0" + authors = [] + "#) + .file("a/src/lib.rs", ""); + + assert_that(p.cargo_process("build").arg("-v"), + execs().with_status(0) + .with_stdout(format!("\ +{compiling} a v0.5.0 (file://[..]) +{running} `rustc [..] --crate-name a [..]` +{compiling} foo v0.5.0 (file://[..]) +{running} `rustc build.rs [..] --extern a=[..]` +{running} `[..]foo-[..]build-script-build[..]` +{running} `rustc [..] --crate-name foo [..]` +", compiling = COMPILING, running = RUNNING).as_slice())); +}) + +test!(build_deps_not_for_normal { + let (_, target) = ::cargo::ops::rustc_version().unwrap(); + let p = project("foo") + .file("Cargo.toml", r#" + [project] + name = "foo" + version = "0.5.0" + authors = [] + build = "build.rs" + [build-dependencies.a] + path = "a" + "#) + .file("src/lib.rs", "extern crate a;") + .file("build.rs", " + extern crate a; + fn main() {} + ") + .file("a/Cargo.toml", r#" + [project] + name = "a" + version = "0.5.0" + authors = [] + "#) + .file("a/src/lib.rs", ""); + + assert_that(p.cargo_process("build").arg("-v").arg("--target").arg(target), + execs().with_status(101) + .with_stderr("\ +[..]lib.rs[..] error: can't find crate for `a` +[..]lib.rs[..] extern crate a; +[..] ^~~~~~~~~~~~~~~ +error: aborting due to previous error +Could not compile `foo`. + +Caused by: + Process didn't exit successfully: [..] +")); +}) + +test!(build_cmd_with_a_build_cmd { + let p = project("foo") + .file("Cargo.toml", r#" + [project] + name = "foo" + version = "0.5.0" + authors = [] + build = "build.rs" + + [build-dependencies.a] + path = "a" + "#) + .file("src/lib.rs", "") + .file("build.rs", " + extern crate a; + fn main() {} + ") + .file("a/Cargo.toml", r#" + [project] + name = "a" + version = "0.5.0" + authors = [] + build = "build.rs" + + [build-dependencies.b] + path = "../b" + "#) + .file("a/src/lib.rs", "") + .file("a/build.rs", "extern crate b; fn main() {}") + .file("b/Cargo.toml", r#" + [project] + name = "b" + version = "0.5.0" + authors = [] + "#) + .file("b/src/lib.rs", ""); + + assert_that(p.cargo_process("build").arg("-v"), + execs().with_status(0) + .with_stdout(format!("\ +{compiling} b v0.5.0 (file://[..]) +{running} `rustc [..] --crate-name b [..]` +{compiling} a v0.5.0 (file://[..]) +{running} `rustc build.rs [..] --extern b=[..]` +{running} `[..]a-[..]build-script-build[..]` +{running} `rustc [..]lib.rs --crate-name a --crate-type lib -g \ + -C metadata=[..] -C extra-filename=-[..] \ + --out-dir [..]target[..]deps --dep-info [..]fingerprint[..]dep-lib-a \ + -L [..]target[..]deps -L [..]target[..]deps` +{compiling} foo v0.5.0 (file://[..]) +{running} `rustc build.rs --crate-name build-script-build --crate-type bin \ + -C prefer-dynamic -g \ + --out-dir [..]build[..]foo-[..] --dep-info [..]fingerprint[..]dep-[..] \ + -L [..]target -L [..]target[..]deps \ + --extern a=[..]liba-[..].rlib` +{running} `[..]foo-[..]build-script-build[..]` +{running} `rustc [..]lib.rs --crate-name foo --crate-type lib -g \ + -C metadata=[..] -C extra-filename=-[..] \ + --out-dir [..]target --dep-info [..]fingerprint[..]dep-lib-foo \ + -L [..]target -L [..]target[..]deps` +", compiling = COMPILING, running = RUNNING).as_slice())); +}) + +test!(out_dir_is_preserved { + let p = project("foo") + .file("Cargo.toml", r#" + [project] + name = "foo" + version = "0.5.0" + authors = [] + build = "build.rs" + "#) + .file("src/lib.rs", "") + .file("build.rs", r#" + use std::os; + use std::io::File; + fn main() { + let out = os::getenv("OUT_DIR").unwrap(); + File::create(&Path::new(out).join("foo")).unwrap(); + } + "#); + + // Make the file + assert_that(p.cargo_process("build").arg("-v"), + execs().with_status(0)); + p.root().move_into_the_past().unwrap(); + + // Change to asserting that it's there + File::create(&p.root().join("build.rs")).write_str(r#" + use std::os; + use std::io::File; + fn main() { + let out = os::getenv("OUT_DIR").unwrap(); + File::open(&Path::new(out).join("foo")).unwrap(); + } + "#).unwrap(); + p.root().move_into_the_past().unwrap(); + assert_that(p.process(cargo_dir().join("cargo")).arg("build").arg("-v"), + execs().with_status(0)); + + // Run a fresh build where file should be preserved + assert_that(p.process(cargo_dir().join("cargo")).arg("build").arg("-v"), + execs().with_status(0)); + + // One last time to make sure it's still there. + File::create(&p.root().join("foo")).unwrap(); + assert_that(p.process(cargo_dir().join("cargo")).arg("build").arg("-v"), + execs().with_status(0)); +}) + +test!(output_separate_lines { + let p = project("foo") + .file("Cargo.toml", r#" + [project] + name = "foo" + version = "0.5.0" + authors = [] + build = "build.rs" + "#) + .file("src/lib.rs", "") + .file("build.rs", r#" + fn main() { + println!("cargo:rustc-flags=-L foo"); + println!("cargo:rustc-flags=-l foo:static"); + } + "#); + assert_that(p.cargo_process("build").arg("-v"), + execs().with_status(101) + .with_stdout(format!("\ +{compiling} foo v0.5.0 (file://[..]) +{running} `rustc build.rs [..]` +{running} `[..]foo-[..]build-script-build[..]` +{running} `rustc [..] --crate-name foo [..] -L foo -l foo:static` +", compiling = COMPILING, running = RUNNING).as_slice())); +}) + +test!(code_generation { + let p = project("foo") + .file("Cargo.toml", r#" + [project] + name = "foo" + version = "0.5.0" + authors = [] + build = "build.rs" + "#) + .file("src/main.rs", r#" + include!(concat!(env!("OUT_DIR"), "/hello.rs")) + + fn main() { + println!("{}", message()); + } + "#) + .file("build.rs", r#" + use std::os; + use std::io::File; + + fn main() { + let dst = Path::new(os::getenv("OUT_DIR").unwrap()); + let mut f = File::create(&dst.join("hello.rs")).unwrap(); + f.write_str(" + pub fn message() -> &'static str { + \"Hello, World!\" + } + ").unwrap(); + } + "#); + assert_that(p.cargo_process("run"), + execs().with_status(0) + .with_stdout(format!("\ +{compiling} foo v0.5.0 (file://[..]) +{running} `target[..]foo` +Hello, World! +", compiling = COMPILING, running = RUNNING).as_slice())); +}) diff --git a/tests/test_cargo_compile_git_deps.rs b/tests/test_cargo_compile_git_deps.rs index 6f80894868b..8d8f45deded 100644 --- a/tests/test_cargo_compile_git_deps.rs +++ b/tests/test_cargo_compile_git_deps.rs @@ -1175,9 +1175,9 @@ test!(git_repo_changing_no_rebuild { assert_that(p1.process(cargo_dir().join("cargo")).arg("build"), execs().with_stdout(format!("\ {updating} git repository `{bar}` -{compiling} bar v0.5.0 ({bar}#[..]) -{compiling} p1 v0.5.0 ({url}) -", updating = UPDATING, compiling = COMPILING, url = p1.url(), bar = bar.url()))); +{compiling} [..] +{compiling} [..] +", updating = UPDATING, compiling = COMPILING, bar = bar.url()))); // Make a commit to lock p2 to a different rev File::create(&bar.root().join("src/lib.rs")).write_str(r#" @@ -1201,9 +1201,9 @@ test!(git_repo_changing_no_rebuild { assert_that(p2.cargo_process("build"), execs().with_stdout(format!("\ {updating} git repository `{bar}` -{compiling} bar v0.5.0 ({bar}#[..]) -{compiling} p2 v0.5.0 ({url}) -", updating = UPDATING, compiling = COMPILING, url = p2.url(), bar = bar.url()))); +{compiling} [..] +{compiling} [..] +", updating = UPDATING, compiling = COMPILING, bar = bar.url()))); // And now for the real test! Make sure that p1 doesn't get rebuilt // even though the git repo has changed. diff --git a/tests/test_cargo_compile_old_custom_build.rs b/tests/test_cargo_compile_old_custom_build.rs new file mode 100644 index 00000000000..63a8081f2ef --- /dev/null +++ b/tests/test_cargo_compile_old_custom_build.rs @@ -0,0 +1,403 @@ +use std::path; + +use support::{project, execs, cargo_dir}; +use hamcrest::{assert_that}; + +fn setup() { +} + +test!(old_custom_build { + let mut build = project("builder"); + build = build + .file("Cargo.toml", r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [[bin]] name = "foo" + "#) + .file("src/foo.rs", r#" + fn main() { println!("Hello!"); } + "#); + assert_that(build.cargo_process("build"), + execs().with_status(0)); + + + let mut p = project("foo"); + p = p + .file("Cargo.toml", format!(r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + build = '{}' + + [[bin]] name = "foo" + "#, build.bin("foo").display())) + .file("src/foo.rs", r#" + fn main() {} + "#); + assert_that(p.cargo_process("build"), + execs().with_status(0) + .with_stdout(format!(" Compiling foo v0.5.0 ({})\n", + p.url())) + .with_stderr("warning: the old build command has been deprecated")); +}) + +test!(old_custom_multiple_build { + let mut build1 = project("builder1"); + build1 = build1 + .file("Cargo.toml", r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [[bin]] name = "foo" + "#) + .file("src/foo.rs", r#" + fn main() { + let args = ::std::os::args(); + assert_eq!(args[1], "hello".to_string()); + assert_eq!(args[2], "world".to_string()); + } + "#); + assert_that(build1.cargo_process("build"), + execs().with_status(0)); + + let mut build2 = project("builder2"); + build2 = build2 + .file("Cargo.toml", r#" + [project] + + name = "bar" + version = "0.5.0" + authors = ["wycats@example.com"] + + [[bin]] name = "bar" + "#) + .file("src/bar.rs", r#" + fn main() { + let args = ::std::os::args(); + assert_eq!(args[1], "cargo".to_string()); + } + "#); + assert_that(build2.cargo_process("build"), + execs().with_status(0)); + + let mut p = project("foo"); + p = p + .file("Cargo.toml", format!(r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + build = [ '{} hello world', '{} cargo' ] + + [[bin]] name = "foo" + "#, build1.bin("foo").display(), build2.bin("bar").display())) + .file("src/foo.rs", r#" + fn main() {} + "#); + assert_that(p.cargo_process("build"), + execs().with_status(0) + .with_stdout(format!(" Compiling foo v0.5.0 ({})\n", + p.url())) + .with_stderr("warning: the old build command has been deprecated")); +}) + +test!(old_custom_build_failure { + let mut build = project("builder"); + build = build + .file("Cargo.toml", r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [[bin]] + name = "foo" + "#) + .file("src/foo.rs", r#" + fn main() { panic!("nope") } + "#); + assert_that(build.cargo_process("build"), execs().with_status(0)); + + + let mut p = project("foo"); + p = p + .file("Cargo.toml", format!(r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + build = '{}' + + [[bin]] + name = "foo" + "#, build.bin("foo").display())) + .file("src/foo.rs", r#" + fn main() {} + "#); + assert_that(p.cargo_process("build"), + execs().with_status(101).with_stderr(format!("\ +warning: the old build command has been deprecated\n\ +Failed to run custom build command for `foo v0.5.0 ({dir})` +Process didn't exit successfully: `{}` (status=101)\n\ +--- stderr\n\ +task '
' panicked at 'nope', {filename}:2\n\ +\n\ +", build.bin("foo").display(), filename = format!("src{}foo.rs", path::SEP), + dir = p.url()))); +}) + +test!(old_custom_second_build_failure { + let mut build1 = project("builder1"); + build1 = build1 + .file("Cargo.toml", r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [[bin]] name = "foo" + "#) + .file("src/foo.rs", r#" + fn main() { println!("Hello!"); } + "#); + assert_that(build1.cargo_process("build"), + execs().with_status(0)); + + let mut build2 = project("builder2"); + build2 = build2 + .file("Cargo.toml", r#" + [project] + + name = "bar" + version = "0.5.0" + authors = ["wycats@example.com"] + + [[bin]] + name = "bar" + "#) + .file("src/bar.rs", r#" + fn main() { panic!("nope") } + "#); + assert_that(build2.cargo_process("build"), execs().with_status(0)); + + + let mut p = project("foo"); + p = p + .file("Cargo.toml", format!(r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + build = [ '{}', '{}' ] + + [[bin]] + name = "foo" + "#, build1.bin("foo").display(), build2.bin("bar").display())) + .file("src/foo.rs", r#" + fn main() {} + "#); + assert_that(p.cargo_process("build"), + execs().with_status(101).with_stderr(format!("\ +warning: the old build command has been deprecated\n\ +Failed to run custom build command for `foo v0.5.0 ({dir})` +Process didn't exit successfully: `{}` (status=101)\n\ +--- stderr\n\ +task '
' panicked at 'nope', {filename}:2\n\ +\n\ +", build2.bin("bar").display(), filename = format!("src{}bar.rs", path::SEP), + dir = p.url()))); +}) + +test!(old_custom_build_env_vars { + let bar = project("bar") + .file("Cargo.toml", r#" + [package] + name = "bar-bar" + version = "0.0.1" + authors = [] + build = "true" + "#) + .file("src/lib.rs", ""); + bar.build(); + + let mut p = project("foo"); + let mut build = project("builder"); + build = build + .file("Cargo.toml", r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [features] + foo = [] + + [[bin]] + name = "foo" + "#) + .file("src/foo.rs", format!(r#" + use std::os; + use std::io::fs::PathExtensions; + fn main() {{ + let _ncpus = os::getenv("NUM_JOBS").unwrap(); + let _feat = os::getenv("CARGO_FEATURE_FOO").unwrap(); + let debug = os::getenv("DEBUG").unwrap(); + assert_eq!(debug.as_slice(), "true"); + + let opt = os::getenv("OPT_LEVEL").unwrap(); + assert_eq!(opt.as_slice(), "0"); + + let opt = os::getenv("PROFILE").unwrap(); + assert_eq!(opt.as_slice(), "compile"); + + let out = os::getenv("OUT_DIR").unwrap(); + assert!(out.as_slice().starts_with(r"{0}")); + assert!(Path::new(out).is_dir()); + + let out = os::getenv("DEP_BAR_BAR_OUT_DIR").unwrap(); + assert!(out.as_slice().starts_with(r"{0}")); + assert!(Path::new(out).is_dir()); + + let out = os::getenv("CARGO_MANIFEST_DIR").unwrap(); + let p1 = Path::new(out); + let p2 = os::make_absolute(&Path::new(file!()).dir_path().dir_path()); + assert!(p1 == p2, "{{}} != {{}}", p1.display(), p2.display()); + }} + "#, + p.root().join("target").join("native").display())); + assert_that(build.cargo_process("build").arg("--features").arg("foo"), + execs().with_status(0)); + + + p = p + .file("Cargo.toml", format!(r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + build = '{}' + + [features] + foo = [] + + [[bin]] + name = "foo" + + [dependencies.bar-bar] + path = '{}' + "#, build.bin("foo").display(), bar.root().display())) + .file("src/foo.rs", r#" + fn main() {} + "#); + assert_that(p.cargo_process("build").arg("--features").arg("foo"), + execs().with_status(0)); +}) + +test!(old_custom_build_in_dependency { + let mut p = project("foo"); + let mut build = project("builder"); + build = build + .file("Cargo.toml", r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [[bin]] + name = "foo" + "#) + .file("src/foo.rs", format!(r#" + use std::os; + fn main() {{ + assert!(os::getenv("OUT_DIR").unwrap().as_slice() + .starts_with(r"{}")); + }} + "#, + p.root().join("target/native/bar-").display())); + assert_that(build.cargo_process("build"), execs().with_status(0)); + + + p = p + .file("Cargo.toml", r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [[bin]] + name = "foo" + [dependencies.bar] + path = "bar" + "#) + .file("src/foo.rs", r#" + extern crate bar; + fn main() { bar::bar() } + "#) + .file("bar/Cargo.toml", format!(r#" + [project] + + name = "bar" + version = "0.5.0" + authors = ["wycats@example.com"] + build = '{}' + "#, build.bin("foo").display())) + .file("bar/src/lib.rs", r#" + pub fn bar() {} + "#); + assert_that(p.cargo_process("build"), + execs().with_status(0)); +}) + +// tests that custom build in dep can be built twice in a row - issue 227 +test!(old_custom_build_in_dependency_twice { + let p = project("foo") + .file("Cargo.toml", r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [[bin]] + name = "foo" + [dependencies.bar] + path = "./bar" + "#) + .file("src/foo.rs", r#" + extern crate bar; + fn main() { bar::bar() } + "#) + .file("bar/Cargo.toml", format!(r#" + [project] + + name = "bar" + version = "0.0.1" + authors = ["wycats@example.com"] + build = '{}' + "#, "echo test")) + .file("bar/src/lib.rs", r#" + pub fn bar() {} + "#); + assert_that(p.cargo_process("build"), + execs().with_status(0)); + assert_that(p.process(cargo_dir().join("cargo")).arg("build"), + execs().with_status(0)); +}) diff --git a/tests/test_cargo_compile_plugins.rs b/tests/test_cargo_compile_plugins.rs index b59c8df3411..2d6a7bd9f9e 100644 --- a/tests/test_cargo_compile_plugins.rs +++ b/tests/test_cargo_compile_plugins.rs @@ -83,15 +83,15 @@ test!(plugin_to_the_max { }) test!(plugin_with_dynamic_native_dependency { - let build = project("build") + let build = project("builder") .file("Cargo.toml", r#" [package] - name = "build" + name = "builder" version = "0.0.1" authors = [] [lib] - name = "build" + name = "builder" crate-type = ["dylib"] "#) .file("src/main.rs", r#" @@ -147,7 +147,7 @@ test!(plugin_with_dynamic_native_dependency { [lib] name = "bar" plugin = true - "#, build.bin("build").display())) + "#, build.bin("builder").display())) .file("bar/src/lib.rs", format!(r#" #![feature(plugin_registrar)] diff --git a/tests/test_cargo_publish.rs b/tests/test_cargo_publish.rs index bf291d6fbf8..bca369b6797 100644 --- a/tests/test_cargo_publish.rs +++ b/tests/test_cargo_publish.rs @@ -90,7 +90,7 @@ test!(git_deps { assert_that(p.cargo_process("publish").arg("-v").arg("--no-verify"), execs().with_status(101).with_stderr("\ -all dependencies must come from the same registry +all dependencies must come from the same registry. dependency `foo` comes from git://path/to/nowhere instead ")); }) @@ -118,7 +118,7 @@ test!(path_dependency_no_version { assert_that(p.cargo_process("publish"), execs().with_status(101).with_stderr("\ all path dependencies must have a version specified when being uploaded \ -to the registry +to the registry. dependency `bar` does not specify a version ")); }) diff --git a/tests/test_cargo_test.rs b/tests/test_cargo_test.rs index 450ae4f1a28..3b25ae4534a 100644 --- a/tests/test_cargo_test.rs +++ b/tests/test_cargo_test.rs @@ -36,7 +36,7 @@ test!(cargo_test_simple { assert_that(p.process(cargo_dir().join("cargo")).arg("test"), execs().with_stdout(format!("\ {} foo v0.5.0 ({}) -{} target[..]foo +{} target[..]foo-[..] running 1 test test test_hello ... ok @@ -127,7 +127,7 @@ test!(cargo_test_failing_test { assert_that(p.process(cargo_dir().join("cargo")).arg("test"), execs().with_stdout(format!("\ {} foo v0.5.0 ({}) -{} target[..]foo +{} target[..]foo-[..] running 1 test test test_hello ... FAILED @@ -198,7 +198,7 @@ test bin_test ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured -{running} target[..]foo +{running} target[..]foo[..] running 1 test test lib_test ... ok @@ -413,7 +413,7 @@ test!(pass_through_command_line { execs().with_status(0) .with_stdout(format!("\ {compiling} foo v0.0.1 ({dir}) -{running} target[..]foo +{running} target[..]foo-[..] running 1 test test bar ... ok @@ -435,7 +435,7 @@ test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured execs().with_status(0) .with_stdout(format!("\ {compiling} foo v0.0.1 ({dir}) -{running} target[..]foo +{running} target[..]foo-[..] running 1 test test foo ... ok diff --git a/tests/tests.rs b/tests/tests.rs index 6807fae6ded..0c0a47851cf 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -29,6 +29,8 @@ mod test_cargo; mod test_cargo_bench; mod test_cargo_clean; mod test_cargo_compile; +mod test_cargo_compile_custom_build; +mod test_cargo_compile_old_custom_build; mod test_cargo_compile_git_deps; mod test_cargo_compile_path_deps; mod test_cargo_test;