diff --git a/src/cargo/ops/cargo_compile.rs b/src/cargo/ops/cargo_compile.rs index ba7716f12b5..8aa6b987817 100644 --- a/src/cargo/ops/cargo_compile.rs +++ b/src/cargo/ops/cargo_compile.rs @@ -32,7 +32,7 @@ use core::{Source, SourceId, SourceMap, PackageSet, Package, Target}; use core::{Profile, TargetKind, Profiles}; use core::resolver::{Method, Resolve}; use ops::{self, BuildOutput, ExecEngine}; -use util::config::{ConfigValue, Config}; +use util::config::Config; use util::{CargoResult, internal, ChainError, profile}; /// Contains information about how a package should be compiled. @@ -421,19 +421,21 @@ fn scrape_build_config(config: &Config, target: Option) -> CargoResult { let cfg_jobs = match try!(config.get_i64("build.jobs")) { - Some((n, p)) => { - if n <= 0 { - bail!("build.jobs must be positive, but found {} in {:?}", n, p) - } else if n >= u32::max_value() as i64 { - bail!("build.jobs is too large: found {} in {:?}", n, p) + Some(v) => { + if v.val <= 0 { + bail!("build.jobs must be positive, but found {} in {}", + v.val, v.definition) + } else if v.val >= u32::max_value() as i64 { + bail!("build.jobs is too large: found {} in {}", v.val, + v.definition) } else { - Some(n as u32) + Some(v.val as u32) } } None => None, }; let jobs = jobs.or(cfg_jobs).unwrap_or(::num_cpus::get() as u32); - let cfg_target = try!(config.get_string("build.target")).map(|s| s.0); + let cfg_target = try!(config.get_string("build.target")).map(|s| s.val); let target = target.or(cfg_target); let mut base = ops::BuildConfig { jobs: jobs, @@ -453,16 +455,18 @@ fn scrape_target_config(config: &Config, triple: &str) let key = format!("target.{}", triple); let mut ret = ops::TargetConfig { - ar: try!(config.get_path(&format!("{}.ar", key))), - linker: try!(config.get_path(&format!("{}.linker", key))), + ar: try!(config.get_path(&format!("{}.ar", key))).map(|v| v.val), + linker: try!(config.get_path(&format!("{}.linker", key))).map(|v| v.val), overrides: HashMap::new(), }; let table = match try!(config.get_table(&key)) { - Some((table, _)) => table, + Some(table) => table.val, None => return Ok(ret), }; for (lib_name, _) in table.into_iter() { - if lib_name == "ar" || lib_name == "linker" { continue } + if lib_name == "ar" || lib_name == "linker" { + continue + } let mut output = BuildOutput { library_paths: Vec::new(), @@ -472,40 +476,39 @@ fn scrape_target_config(config: &Config, triple: &str) rerun_if_changed: Vec::new(), }; let key = format!("{}.{}", key, lib_name); - let table = try!(config.get_table(&key)).unwrap().0; + let table = try!(config.get_table(&key)).unwrap().val; for (k, _) in table.into_iter() { let key = format!("{}.{}", key, k); - match try!(config.get(&key)).unwrap() { - ConfigValue::String(v, path) => { - if k == "rustc-flags" { - let whence = format!("in `{}` (in {})", key, - path.display()); - let (paths, links) = try!( - BuildOutput::parse_rustc_flags(&v, &whence) - ); - output.library_paths.extend(paths.into_iter()); - output.library_links.extend(links.into_iter()); - } else { - output.metadata.push((k, v)); - } - }, - ConfigValue::List(a, p) => { - if k == "rustc-link-lib" { - output.library_links.extend(a.into_iter().map(|v| v.0)); - } else if k == "rustc-link-search" { - output.library_paths.extend(a.into_iter().map(|v| { - PathBuf::from(&v.0) - })); - } else if k == "rustc-cfg" { - output.cfgs.extend(a.into_iter().map(|v| v.0)); - } else { - try!(config.expected("string", &k, - ConfigValue::List(a, p))); - } - }, - // technically could be a list too, but that's the exception to - // the rule... - cv => { try!(config.expected("string", &k, cv)); } + match &k[..] { + "rustc-flags" => { + let flags = try!(config.get_string(&key)).unwrap(); + let whence = format!("in `{}` (in {})", key, + flags.definition); + let (paths, links) = try!( + BuildOutput::parse_rustc_flags(&flags.val, &whence) + ); + output.library_paths.extend(paths.into_iter()); + output.library_links.extend(links.into_iter()); + } + "rustc-link-lib" => { + let list = try!(config.get_list(&key)).unwrap(); + output.library_links.extend(list.val.into_iter() + .map(|v| v.0)); + } + "rustc-link-search" => { + let list = try!(config.get_list(&key)).unwrap(); + output.library_paths.extend(list.val.into_iter().map(|v| { + PathBuf::from(&v.0) + })); + } + "rustc-cfg" => { + let list = try!(config.get_list(&key)).unwrap(); + output.cfgs.extend(list.val.into_iter().map(|v| v.0)); + } + _ => { + let val = try!(config.get_string(&key)).unwrap(); + output.metadata.push((k, val.val)); + } } } ret.overrides.insert(lib_name, output); diff --git a/src/cargo/ops/cargo_install.rs b/src/cargo/ops/cargo_install.rs index 36fdabaf496..95eb4238eea 100644 --- a/src/cargo/ops/cargo_install.rs +++ b/src/cargo/ops/cargo_install.rs @@ -340,11 +340,11 @@ pub fn uninstall(root: Option<&str>, } fn resolve_root(flag: Option<&str>, config: &Config) -> CargoResult { - let config_root = try!(config.get_string("install.root")); + let config_root = try!(config.get_path("install.root")); Ok(flag.map(PathBuf::from).or_else(|| { env::var_os("CARGO_INSTALL_ROOT").map(PathBuf::from) - }).or_else(|| { - config_root.clone().map(|(v, _)| PathBuf::from(v)) + }).or_else(move || { + config_root.map(|v| v.val) }).unwrap_or_else(|| { config.home().to_owned() })) diff --git a/src/cargo/ops/cargo_new.rs b/src/cargo/ops/cargo_new.rs index 309c75c6175..41fa01fc11f 100644 --- a/src/cargo/ops/cargo_new.rs +++ b/src/cargo/ops/cargo_new.rs @@ -63,17 +63,17 @@ fn get_name<'a>(path: &'a Path, opts: &'a NewOptions, config: &Config) -> CargoR if let Some(name) = opts.name { return Ok(name); } - + if path.file_name().is_none() { bail!("cannot auto-detect project name from path {:?} ; use --name to override", path.as_os_str()); } - + let dir_name = try!(path.file_name().and_then(|s| s.to_str()).chain_error(|| { human(&format!("cannot create a project with a non-unicode name: {:?}", path.file_name().unwrap())) })); - + if opts.bin { Ok(dir_name) } else { @@ -99,24 +99,24 @@ fn check_name(name: &str) -> CargoResult<()> { Ok(()) } -fn detect_source_paths_and_types(project_path : &Path, - project_name: &str, +fn detect_source_paths_and_types(project_path : &Path, + project_name: &str, detected_files: &mut Vec, ) -> CargoResult<()> { let path = project_path; let name = project_name; - + enum H { Bin, Lib, Detect, } - + struct Test { proposed_path: String, handling: H, } - + let tests = vec![ Test { proposed_path: format!("src/main.rs"), handling: H::Bin }, Test { proposed_path: format!("main.rs"), handling: H::Bin }, @@ -125,48 +125,48 @@ fn detect_source_paths_and_types(project_path : &Path, Test { proposed_path: format!("src/lib.rs"), handling: H::Lib }, Test { proposed_path: format!("lib.rs"), handling: H::Lib }, ]; - + for i in tests { let pp = i.proposed_path; - + // path/pp does not exist or is not a file if !fs::metadata(&path.join(&pp)).map(|x| x.is_file()).unwrap_or(false) { continue; } - + let sfi = match i.handling { H::Bin => { - SourceFileInformation { - relative_path: pp, - target_name: project_name.to_string(), - bin: true + SourceFileInformation { + relative_path: pp, + target_name: project_name.to_string(), + bin: true } } H::Lib => { - SourceFileInformation { - relative_path: pp, - target_name: project_name.to_string(), + SourceFileInformation { + relative_path: pp, + target_name: project_name.to_string(), bin: false } } H::Detect => { let content = try!(paths::read(&path.join(pp.clone()))); let isbin = content.contains("fn main"); - SourceFileInformation { - relative_path: pp, - target_name: project_name.to_string(), - bin: isbin + SourceFileInformation { + relative_path: pp, + target_name: project_name.to_string(), + bin: isbin } } }; detected_files.push(sfi); } - + // Check for duplicate lib attempt - + let mut previous_lib_relpath : Option<&str> = None; let mut duplicates_checker : BTreeMap<&str, &SourceFileInformation> = BTreeMap::new(); - + for i in detected_files { if i.bin { if let Some(x) = BTreeMap::get::(&duplicates_checker, i.target_name.as_ref()) { @@ -188,13 +188,13 @@ cannot automatically generate Cargo.toml as the main target would be ambiguous", previous_lib_relpath = Some(&i.relative_path); } } - + Ok(()) } fn plan_new_source_file(bin: bool, project_name: String) -> SourceFileInformation { if bin { - SourceFileInformation { + SourceFileInformation { relative_path: "src/main.rs".to_string(), target_name: project_name, bin: true, @@ -214,7 +214,7 @@ pub fn new(opts: NewOptions, config: &Config) -> CargoResult<()> { bail!("destination `{}` already exists", path.display()) } - + let name = try!(get_name(&path, &opts, config)); try!(check_name(name)); @@ -225,7 +225,7 @@ pub fn new(opts: NewOptions, config: &Config) -> CargoResult<()> { source_files: vec![plan_new_source_file(opts.bin, name.to_string())], bin: opts.bin, }; - + mk(config, &mkopts).chain_error(|| { human(format!("Failed to create project `{}` at `{}`", name, path.display())) @@ -234,19 +234,19 @@ pub fn new(opts: NewOptions, config: &Config) -> CargoResult<()> { pub fn init(opts: NewOptions, config: &Config) -> CargoResult<()> { let path = config.cwd().join(opts.path); - + let cargotoml_path = path.join("Cargo.toml"); if fs::metadata(&cargotoml_path).is_ok() { bail!("`cargo init` cannot be run on existing Cargo projects") } - + let name = try!(get_name(&path, &opts, config)); try!(check_name(name)); - + let mut src_paths_types = vec![]; - + try!(detect_source_paths_and_types(&path, name, &mut src_paths_types)); - + if src_paths_types.len() == 0 { src_paths_types.push(plan_new_source_file(opts.bin, name.to_string())); } else { @@ -254,24 +254,24 @@ pub fn init(opts: NewOptions, config: &Config) -> CargoResult<()> { // Maybe when doing `cargo init --bin` inside a library project stub, // user may mean "initialize for library, but also add binary target" } - + let mut version_control = opts.version_control; - + if version_control == None { let mut num_detected_vsces = 0; - + if fs::metadata(&path.join(".git")).is_ok() { version_control = Some(VersionControl::Git); num_detected_vsces += 1; } - + if fs::metadata(&path.join(".hg")).is_ok() { version_control = Some(VersionControl::Hg); num_detected_vsces += 1; } - + // if none exists, maybe create git, like in `cargo new` - + if num_detected_vsces > 1 { bail!("both .git and .hg directories found \ and the ignore file can't be \ @@ -279,7 +279,7 @@ pub fn init(opts: NewOptions, config: &Config) -> CargoResult<()> { specify --vcs to override detection"); } } - + let mkopts = MkOptions { version_control: version_control, path: &path, @@ -287,7 +287,7 @@ pub fn init(opts: NewOptions, config: &Config) -> CargoResult<()> { bin: src_paths_types.iter().any(|x|x.bin), source_files: src_paths_types, }; - + mk(config, &mkopts).chain_error(|| { human(format!("Failed to create project `{}` at `{}`", name, path.display())) @@ -357,11 +357,11 @@ fn mk(config: &Config, opts: &MkOptions) -> CargoResult<()> { (Some(name), None, _, None) | (None, None, name, None) => name, }; - + let mut cargotoml_path_specifier = String::new(); - + // Calculare what [lib] and [[bin]]s do we need to append to Cargo.toml - + for i in &opts.source_files { if i.bin { if i.relative_path != "src/main.rs" { @@ -394,17 +394,17 @@ authors = [{}] {}"#, name, toml::Value::String(author), cargotoml_path_specifier).as_bytes())); - // Create all specified source files - // (with respective parent directories) + // Create all specified source files + // (with respective parent directories) // if they are don't exist for i in &opts.source_files { let path_of_source_file = path.join(i.relative_path.clone()); - + if let Some(src_dir) = path_of_source_file.parent() { try!(fs::create_dir_all(src_dir)); } - + let default_file_content : &[u8] = if i.bin { b"\ fn main() { @@ -421,7 +421,7 @@ mod tests { } " }; - + if !fs::metadata(&path_of_source_file).map(|x| x.is_file()).unwrap_or(false) { return paths::write(&path_of_source_file, default_file_content) } @@ -454,18 +454,18 @@ fn discover_author() -> CargoResult<(String, Option)> { } fn global_config(config: &Config) -> CargoResult { - let name = try!(config.get_string("cargo-new.name")).map(|s| s.0); - let email = try!(config.get_string("cargo-new.email")).map(|s| s.0); + let name = try!(config.get_string("cargo-new.name")).map(|s| s.val); + let email = try!(config.get_string("cargo-new.email")).map(|s| s.val); let vcs = try!(config.get_string("cargo-new.vcs")); - let vcs = match vcs.as_ref().map(|p| (&p.0[..], &p.1)) { + let vcs = match vcs.as_ref().map(|p| (&p.val[..], &p.definition)) { Some(("git", _)) => Some(VersionControl::Git), Some(("hg", _)) => Some(VersionControl::Hg), Some(("none", _)) => Some(VersionControl::NoVcs), Some((s, p)) => { return Err(internal(format!("invalid configuration for key \ `cargo-new.vcs`, unknown vcs `{}` \ - (found in {:?})", s, p))) + (found in {})", s, p))) } None => None }; diff --git a/src/cargo/ops/registry.rs b/src/cargo/ops/registry.rs index 9bbdf87127a..0b2f83e18a6 100644 --- a/src/cargo/ops/registry.rs +++ b/src/cargo/ops/registry.rs @@ -126,8 +126,8 @@ fn transmit(pkg: &Package, tarball: &Path, registry: &mut Registry) } pub fn registry_configuration(config: &Config) -> CargoResult { - let index = try!(config.get_string("registry.index")).map(|p| p.0); - let token = try!(config.get_string("registry.token")).map(|p| p.0); + let index = try!(config.get_string("registry.index")).map(|p| p.val); + let token = try!(config.get_string("registry.token")).map(|p| p.val); Ok(RegistryConfig { index: index, token: token }) } @@ -182,7 +182,7 @@ pub fn http_handle(config: &Config) -> CargoResult { /// via environment variables are picked up by libcurl. fn http_proxy(config: &Config) -> CargoResult> { match try!(config.get_string("http.proxy")) { - Some((s, _)) => return Ok(Some(s)), + Some(s) => return Ok(Some(s.val)), None => {} } match git2::Config::open_default() { @@ -218,7 +218,7 @@ pub fn http_proxy_exists(config: &Config) -> CargoResult { pub fn http_timeout(config: &Config) -> CargoResult> { match try!(config.get_i64("http.timeout")) { - Some((s, _)) => return Ok(Some(s)), + Some(s) => return Ok(Some(s.val)), None => {} } Ok(env::var("HTTP_TIMEOUT").ok().and_then(|s| s.parse().ok())) diff --git a/src/cargo/util/config.rs b/src/cargo/util/config.rs index 55c6543731f..00df39b703c 100644 --- a/src/cargo/util/config.rs +++ b/src/cargo/util/config.rs @@ -7,12 +7,13 @@ use std::fs::{self, File}; use std::io::prelude::*; use std::mem; use std::path::{Path, PathBuf}; +use std::str::FromStr; use rustc_serialize::{Encodable,Encoder}; use toml; use core::shell::{Verbosity, ColorConfig}; use core::{MultiShell, Package}; -use util::{CargoResult, ChainError, Rustc, internal, human, paths}; +use util::{CargoResult, CargoError, ChainError, Rustc, internal, human, paths}; use util::toml as cargo_toml; @@ -117,7 +118,7 @@ impl Config { *self.target_dir.borrow_mut() = Some(path.to_owned()); } - pub fn get(&self, key: &str) -> CargoResult> { + fn get(&self, key: &str) -> CargoResult> { let vals = try!(self.values()); let mut parts = key.split('.').enumerate(); let mut val = match vals.get(parts.next().unwrap().1) { @@ -148,50 +149,99 @@ impl Config { Ok(Some(val.clone())) } - pub fn get_string(&self, key: &str) -> CargoResult> { + fn get_env(&self, key: &str) -> CargoResult>> + where Box: From + { + let key = key.replace(".", "_") + .replace("-", "_") + .chars() + .flat_map(|c| c.to_uppercase()) + .collect::(); + match env::var(&format!("CARGO_{}", key)) { + Ok(value) => { + Ok(Some(Value { + val: try!(value.parse()), + definition: Definition::Environment, + })) + } + Err(..) => Ok(None), + } + } + + pub fn get_string(&self, key: &str) -> CargoResult>> { + if let Some(v) = try!(self.get_env(key)) { + return Ok(Some(v)) + } match try!(self.get(key)) { - Some(CV::String(i, path)) => Ok(Some((i, path))), + Some(CV::String(i, path)) => { + Ok(Some(Value { + val: i, + definition: Definition::Path(path), + })) + } Some(val) => self.expected("string", key, val), None => Ok(None), } } - pub fn get_path(&self, key: &str) -> CargoResult> { - if let Some((specified_path, path_to_config)) = try!(self.get_string(&key)) { - if specified_path.contains("/") || (cfg!(windows) && specified_path.contains("\\")) { - // An absolute or a relative path - let prefix_path = path_to_config.parent().unwrap().parent().unwrap(); - // Joining an absolute path to any path results in the given absolute path - Ok(Some(prefix_path.join(specified_path))) + pub fn get_path(&self, key: &str) -> CargoResult>> { + if let Some(val) = try!(self.get_string(&key)) { + let is_path = val.val.contains("/") || + (cfg!(windows) && val.val.contains("\\")); + let path = if is_path { + val.definition.root(self).join(val.val) } else { // A pathless name - Ok(Some(PathBuf::from(specified_path))) - } + PathBuf::from(val.val) + }; + Ok(Some(Value { + val: path, + definition: val.definition, + })) } else { Ok(None) } } - pub fn get_list(&self, key: &str) -> CargoResult, PathBuf)>> { + pub fn get_list(&self, key: &str) + -> CargoResult>>> { match try!(self.get(key)) { - Some(CV::List(i, path)) => Ok(Some((i, path))), + Some(CV::List(i, path)) => { + Ok(Some(Value { + val: i, + definition: Definition::Path(path), + })) + } Some(val) => self.expected("list", key, val), None => Ok(None), } } pub fn get_table(&self, key: &str) - -> CargoResult, PathBuf)>> { + -> CargoResult>>> { match try!(self.get(key)) { - Some(CV::Table(i, path)) => Ok(Some((i, path))), + Some(CV::Table(i, path)) => { + Ok(Some(Value { + val: i, + definition: Definition::Path(path), + })) + } Some(val) => self.expected("table", key, val), None => Ok(None), } } - pub fn get_i64(&self, key: &str) -> CargoResult> { + pub fn get_i64(&self, key: &str) -> CargoResult>> { + if let Some(v) = try!(self.get_env(key)) { + return Ok(Some(v)) + } match try!(self.get(key)) { - Some(CV::Integer(i, path)) => Ok(Some((i, path))), + Some(CV::Integer(i, path)) => { + Ok(Some(Value { + val: i, + definition: Definition::Path(path), + })) + } Some(val) => self.expected("integer", key, val), None => Ok(None), } @@ -244,12 +294,8 @@ impl Config { fn scrape_target_dir_config(&mut self) -> CargoResult<()> { if let Some(dir) = env::var_os("CARGO_TARGET_DIR") { *self.target_dir.borrow_mut() = Some(self.cwd.join(dir)); - } else if let Some((dir, dir2)) = try!(self.get_string("build.target-dir")) { - let mut path = PathBuf::from(dir2); - path.pop(); - path.pop(); - path.push(dir); - *self.target_dir.borrow_mut() = Some(path); + } else if let Some(val) = try!(self.get_path("build.target-dir")) { + *self.target_dir.borrow_mut() = Some(val.val); } Ok(()) } @@ -262,7 +308,7 @@ impl Config { let var = format!("build.{}", tool); if let Some(tool_path) = try!(self.get_path(&var)) { - return Ok(tool_path); + return Ok(tool_path.val); } Ok(PathBuf::from(tool)) @@ -284,6 +330,16 @@ pub enum ConfigValue { Boolean(bool, PathBuf), } +pub struct Value { + pub val: T, + pub definition: Definition, +} + +pub enum Definition { + Path(PathBuf), + Environment, +} + impl fmt::Debug for ConfigValue { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { @@ -466,6 +522,24 @@ impl ConfigValue { } } +impl Definition { + pub fn root<'a>(&'a self, config: &'a Config) -> &'a Path { + match *self { + Definition::Path(ref p) => p.parent().unwrap().parent().unwrap(), + Definition::Environment => config.cwd(), + } + } +} + +impl fmt::Display for Definition { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Definition::Path(ref p) => p.display().fmt(f), + Definition::Environment => "the environment".fmt(f), + } + } +} + fn homedir(cwd: &Path) -> Option { let cargo_home = env::var_os("CARGO_HOME").map(|home| { cwd.join(home) diff --git a/src/cargo/util/errors.rs b/src/cargo/util/errors.rs index 255d8699e84..d69d6b3002a 100644 --- a/src/cargo/util/errors.rs +++ b/src/cargo/util/errors.rs @@ -2,8 +2,10 @@ use std::error::Error; use std::ffi; use std::fmt; use std::io; +use std::num; use std::process::{Output, ExitStatus}; use std::str; +use std::string; use curl; use git2; @@ -308,6 +310,13 @@ from_error! { toml::DecodeError, ffi::NulError, term::Error, + num::ParseIntError, +} + +impl From for Box { + fn from(t: string::ParseError) -> Box { + match t {} + } } impl From> for Box { @@ -328,6 +337,7 @@ impl CargoError for toml::DecodeError {} impl CargoError for url::ParseError {} impl CargoError for ffi::NulError {} impl CargoError for term::Error {} +impl CargoError for num::ParseIntError {} // ============================================================================= // Construction helpers diff --git a/src/doc/config.md b/src/doc/config.md index d911e87bf2f..157f0817259 100644 --- a/src/doc/config.md +++ b/src/doc/config.md @@ -90,8 +90,16 @@ target-dir = "target" # path of where to place all generated artifacts # Environment Variables -Cargo recognizes a few global [environment variables][env] to configure itself. -Settings specified via config files take precedence over those specified via +Cargo can also be configured through environment variables in addition to the +TOML syntax above. For each configuration key above of the form `foo.bar` the +environment variable `CARGO_FOO_BAR` can also be used to define the value. For +example the `build.jobs` key can also be defined by `CARGO_BUILD_JOBS`. + +Environment variables will take precedent over TOML configuration, and currently +only integer, boolean, and string keys are supported to be defined by environment variables. +In addition to the system above, Cargo recognizes a few other specific +[environment variables][env]. + [env]: environment-variables.html diff --git a/tests/test_cargo_config.rs b/tests/test_cargo_config.rs new file mode 100644 index 00000000000..6de6aad524f --- /dev/null +++ b/tests/test_cargo_config.rs @@ -0,0 +1,26 @@ +use support::{project, execs}; +use hamcrest::assert_that; + +fn setup() { +} + +test!(read_env_vars_for_config { + let p = project("foo") + .file("Cargo.toml", r#" + [package] + name = "foo" + authors = [] + version = "0.0.0" + build = "build.rs" + "#) + .file("src/lib.rs", "") + .file("build.rs", r#" + use std::env; + fn main() { + assert_eq!(env::var("NUM_JOBS").unwrap(), "100"); + } + "#); + + assert_that(p.cargo_process("build").env("CARGO_BUILD_JOBS", "100"), + execs().with_status(0)); +}); diff --git a/tests/tests.rs b/tests/tests.rs index b67e02a0ffb..6b252a3e2e7 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -66,6 +66,7 @@ mod test_cargo_rustdoc; mod test_cargo_search; mod test_cargo_test; mod test_cargo_tool_paths; +mod test_cargo_config; mod test_cargo_verify_project; mod test_cargo_version; mod test_shell;