diff --git a/Cargo.toml b/Cargo.toml index ee1ce815547..61876c13ddb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "cargo" +name = "cargo-batch" version = "0.62.0" edition = "2021" license = "MIT OR Apache-2.0" @@ -10,9 +10,9 @@ readme = "README.md" description = """ Cargo, a package manager for Rust. """ +autobins = false [lib] -name = "cargo" path = "src/cargo/lib.rs" [dependencies] @@ -70,6 +70,7 @@ itertools = "0.10.0" # See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust` # for more information. rustc-workspace-hack = "1.0.0" +cfg-if = "1.0.0" [target.'cfg(windows)'.dependencies] fwdansi = "1.1.0" @@ -104,7 +105,7 @@ flate2 = { version = "1.0.3", default-features = false, features = ["zlib"] } tar = { version = "0.4.26", default-features = false } [[bin]] -name = "cargo" +name = "cargo-batch" test = false doc = false diff --git a/src/cargo/core/compiler/build_context/mod.rs b/src/cargo/core/compiler/build_context/mod.rs index 624a6e88ae6..07df5c4ae9c 100644 --- a/src/cargo/core/compiler/build_context/mod.rs +++ b/src/cargo/core/compiler/build_context/mod.rs @@ -52,6 +52,8 @@ pub struct BuildContext<'a, 'cfg> { /// The list of all kinds that are involved in this build pub all_kinds: HashSet, + + pub unit_export_dirs: HashMap, } impl<'a, 'cfg> BuildContext<'a, 'cfg> { @@ -85,6 +87,7 @@ impl<'a, 'cfg> BuildContext<'a, 'cfg> { unit_graph, scrape_units, all_kinds, + unit_export_dirs: HashMap::new(), }) } diff --git a/src/cargo/core/compiler/build_context/target_info.rs b/src/cargo/core/compiler/build_context/target_info.rs index 32f767ea718..f22783a57bd 100644 --- a/src/cargo/core/compiler/build_context/target_info.rs +++ b/src/cargo/core/compiler/build_context/target_info.rs @@ -751,8 +751,8 @@ pub struct RustcTargetData<'cfg> { /// Build information for targets that we're building for. This will be /// empty if the `--target` flag is not passed. - target_config: HashMap, - target_info: HashMap, + pub target_config: HashMap, + pub target_info: HashMap, } impl<'cfg> RustcTargetData<'cfg> { diff --git a/src/cargo/core/compiler/context/compilation_files.rs b/src/cargo/core/compiler/context/compilation_files.rs index 80a41cdaeff..796bfef95e9 100644 --- a/src/cargo/core/compiler/context/compilation_files.rs +++ b/src/cargo/core/compiler/context/compilation_files.rs @@ -99,7 +99,7 @@ pub struct CompilationFiles<'a, 'cfg> { /// The target directory layout for the target (if different from then host). pub(super) target: HashMap, /// Additional directory to include a copy of the outputs. - export_dir: Option, + export_dir: HashMap, /// The root targets requested by the user on the command line (does not /// include dependencies). roots: Vec, @@ -153,7 +153,7 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> { ws: cx.bcx.ws, host, target, - export_dir: cx.bcx.build_config.export_dir.clone(), + export_dir: cx.bcx.unit_export_dirs.clone(), roots: cx.bcx.roots.clone(), metas, outputs, @@ -208,10 +208,12 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> { } } + /* /// Additional export directory from `--out-dir`. pub fn export_dir(&self) -> Option { self.export_dir.clone() } + */ /// Directory name to use for a package in the form `NAME-HASH`. /// @@ -404,21 +406,11 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> { } let filename = file_type.uplift_filename(&unit.target); - let uplift_path = if unit.target.is_example() { - // Examples live in their own little world. - self.layout(unit.kind).examples().join(filename) - } else if unit.target.is_custom_build() { - self.build_script_dir(unit).join(filename) + if unit.target.is_custom_build() { + Some(self.build_script_dir(unit).join(filename)) } else { - self.layout(unit.kind).dest().join(filename) - }; - if from_path == uplift_path { - // This can happen with things like examples that reside in the - // same directory, do not have a metadata hash (like on Windows), - // and do not have hyphens. - return None; + None } - Some(uplift_path) } fn calc_outputs( @@ -515,15 +507,10 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> { // If, the `different_binary_name` feature is enabled, the name of the hardlink will // be the name of the binary provided by the user in `Cargo.toml`. let hardlink = self.uplift_to(unit, &file_type, &path); - let export_path = if unit.target.is_custom_build() { - None - } else { - self.export_dir.as_ref().and_then(|export_dir| { - hardlink - .as_ref() - .map(|hardlink| export_dir.join(hardlink.file_name().unwrap())) - }) - }; + let export_path = self + .export_dir + .get(unit) + .map(|export_dir| export_dir.join(file_type.uplift_filename(&unit.target))); outputs.push(OutputFile { path, hardlink, diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs index 5abd06a75bd..7d615537850 100644 --- a/src/cargo/core/compiler/mod.rs +++ b/src/cargo/core/compiler/mod.rs @@ -454,7 +454,6 @@ fn rustc(cx: &mut Context<'_, '_>, unit: &Unit, exec: &Arc) -> Car fn link_targets(cx: &mut Context<'_, '_>, unit: &Unit, fresh: bool) -> CargoResult { let bcx = cx.bcx; let outputs = cx.outputs(unit)?; - let export_dir = cx.files().export_dir(); let package_id = unit.pkg.package_id(); let manifest_path = PathBuf::from(unit.pkg.manifest_path()); let profile = unit.profile.clone(); @@ -482,20 +481,15 @@ fn link_targets(cx: &mut Context<'_, '_>, unit: &Unit, fresh: bool) -> CargoResu if !src.exists() { continue; } - let dst = match output.hardlink.as_ref() { - Some(dst) => dst, - None => { - destinations.push(src.clone()); - continue; - } - }; - destinations.push(dst.clone()); - paths::link_or_copy(src, dst)?; - if let Some(ref path) = output.export_path { - let export_dir = export_dir.as_ref().unwrap(); - paths::create_dir_all(export_dir)?; - + if let Some(path) = &output.hardlink { + paths::create_dir_all(path.parent().unwrap())?; + paths::link_or_copy(src, path)?; + destinations.push(path.clone()); + } + if let Some(path) = &output.export_path { + paths::create_dir_all(path.parent().unwrap())?; paths::link_or_copy(src, path)?; + destinations.push(path.clone()); } } diff --git a/src/cargo/core/package.rs b/src/cargo/core/package.rs index f93c249e5da..9826407edda 100644 --- a/src/cargo/core/package.rs +++ b/src/cargo/core/package.rs @@ -301,8 +301,8 @@ impl hash::Hash for Package { /// This is primarily used to convert a set of `PackageId`s to `Package`s. It /// will download as needed, or used the cached download if available. pub struct PackageSet<'cfg> { - packages: HashMap>, - sources: RefCell>, + pub packages: HashMap>, + pub sources: RefCell>, config: &'cfg Config, multi: Multi, /// Used to prevent reusing the PackageSet to download twice. diff --git a/src/cargo/ops/cargo_compile.rs b/src/cargo/ops/cargo_compile.rs index 2a5d09f520b..f43a180beb0 100644 --- a/src/cargo/ops/cargo_compile.rs +++ b/src/cargo/ops/cargo_compile.rs @@ -602,10 +602,12 @@ pub fn create_bcx<'a, 'cfg>( remove_duplicate_doc(build_config, &units, &mut unit_graph); } - if build_config - .requested_kinds - .iter() - .any(CompileKind::is_host) + // cargo-many: we have to do this always, as we can have dupe units even if + // we're not building for the host. + // if build_config + // .requested_kinds + // .iter() + // .any(CompileKind::is_host) { // Rebuild the unit graph, replacing the explicit host targets with // CompileKind::Host, merging any dependencies shared with build diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 00000000000..5df941d5829 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,256 @@ +#![warn(rust_2018_idioms)] // while we're getting used to 2018 +#![allow(clippy::all)] +#![warn(clippy::needless_borrow)] +#![warn(clippy::redundant_clone)] + +use cargo_batch as cargo; + +use cargo::core::compiler::{ + unit_graph, BuildContext, Context, DefaultExecutor, Executor, UnitInterner, +}; +use cargo::core::shell::Shell; +use cargo::core::Workspace; +use cargo::ops::CompileOptions; +use cargo::util::command_prelude::App; +use cargo::util::{ command_prelude, CliResult, Config}; +use cargo::util::{profile}; +use std::env; +use std::sync::Arc; + +use crate::command_prelude::*; + +fn main() { + #[cfg(feature = "pretty-env-logger")] + pretty_env_logger::init_custom_env("CARGO_LOG"); + #[cfg(not(feature = "pretty-env-logger"))] + env_logger::init_from_env("CARGO_LOG"); + + let mut config = match Config::default() { + Ok(cfg) => cfg, + Err(e) => { + let mut shell = Shell::new(); + cargo::exit_with_error(e.into(), &mut shell) + } + }; + + let result = main2(&mut config); + + match result { + Err(e) => cargo::exit_with_error(e, &mut *config.shell()), + Ok(()) => {} + } +} + +fn main2(config: &mut Config) -> CliResult { + let args: Vec<_> = env::args().collect(); + let mut subargs = args.split(|x| *x == "---"); + + let global_args = subargs.next().unwrap(); + let global_args = App::new("cargo-batch") + .setting(AppSettings::DeriveDisplayOrder) + .setting(AppSettings::AllowExternalSubcommands) + .setting(AppSettings::NoAutoVersion) + .arg(opt("unit-graph", "Output build graph in JSON (unstable)")) + .try_get_matches_from(global_args)?; + + config_configure(config, &global_args)?; + init_git_transports(config); + + let unit_graph = global_args._is_present("unit-graph"); + + struct Command<'a> { + ws: Workspace<'a>, + compile_opts: CompileOptions, + } + + let mut cmds = Vec::new(); + for args in subargs { + let cli = build_cli(); + let args = cli.try_get_matches_from(args)?; + //println!("args opts: {:#?}", args); + + let ws = args.workspace(config)?; + + let mut compile_opts = args.compile_options( + config, + CompileMode::Build, + Some(&ws), + ProfileChecking::Custom, + )?; + if let Some(out_dir) = args.value_of_path("out-dir", config) { + compile_opts.build_config.export_dir = Some(out_dir); + } else if let Some(out_dir) = config.build_config()?.out_dir.as_ref() { + let out_dir = out_dir.resolve_path(config); + compile_opts.build_config.export_dir = Some(out_dir); + } + //if compile_opts.build_config.export_dir.is_some() { + // config + // .cli_unstable() + // .fail_if_stable_opt("--out-dir", 6790)?; + //} + + //println!("compile opts: {:#?}", compile_opts); + cmds.push(Command { ws, compile_opts }); + } + + let interner = UnitInterner::new(); + let mut merged_bcx: Option> = None; + + for cmd in &cmds { + let mut bcx = cargo::ops::create_bcx(&cmd.ws, &cmd.compile_opts, &interner).unwrap(); + if let Some(export_dir) = &cmd.compile_opts.build_config.export_dir { + for root in &bcx.roots { + bcx.unit_export_dirs + .insert(root.clone(), export_dir.clone()); + } + } + + if let Some(merged_bcx) = &mut merged_bcx { + // merge!!! + merged_bcx.unit_graph.extend(bcx.unit_graph); + merged_bcx.roots.extend(bcx.roots); + merged_bcx.unit_export_dirs.extend(bcx.unit_export_dirs); + merged_bcx.all_kinds.extend(bcx.all_kinds); + merged_bcx + .target_data + .target_config + .extend(bcx.target_data.target_config); + merged_bcx + .target_data + .target_info + .extend(bcx.target_data.target_info); + merged_bcx.packages.packages.extend(bcx.packages.packages); + merged_bcx + .packages + .sources + .borrow_mut() + .add_source_map(bcx.packages.sources.into_inner()); + } else { + merged_bcx = Some(bcx) + } + } + + let bcx = merged_bcx.unwrap(); + + if unit_graph { + unit_graph::emit_serialized_unit_graph(&bcx.roots, &bcx.unit_graph, bcx.ws.config())?; + return Ok(()); + } + + let _p = profile::start("compiling"); + let cx = Context::new(&bcx)?; + let exec: Arc = Arc::new(DefaultExecutor); + cx.compile(&exec)?; + + Ok(()) +} + +fn config_configure(config: &mut Config, args: &ArgMatches) -> CliResult { + let arg_target_dir = &args.value_of_path("target-dir", config); + let verbose = args.occurrences_of("verbose") as u32; + // quiet is unusual because it is redefined in some subcommands in order + // to provide custom help text. + let quiet = args.is_present("quiet"); + let color = args.value_of("color"); + let frozen = args.is_present("frozen"); + let locked = args.is_present("locked"); + let offline = args.is_present("offline"); + + let unstable_flags = args + .values_of_lossy("unstable-features") + .unwrap_or_default(); + + let config_args: Vec<_> = args + .values_of("config") + .unwrap_or_default() + .map(|s| s.to_string()) + .collect(); + + config.configure( + verbose, + quiet, + color, + frozen, + locked, + offline, + arg_target_dir, + &unstable_flags, + &config_args, + )?; + Ok(()) +} + +fn init_git_transports(config: &Config) { + // Only use a custom transport if any HTTP options are specified, + // such as proxies or custom certificate authorities. The custom + // transport, however, is not as well battle-tested. + + match cargo::ops::needs_custom_http_transport(config) { + Ok(true) => {} + _ => return, + } + + let handle = match cargo::ops::http_handle(config) { + Ok(handle) => handle, + Err(..) => return, + }; + + // The unsafety of the registration function derives from two aspects: + // + // 1. This call must be synchronized with all other registration calls as + // well as construction of new transports. + // 2. The argument is leaked. + // + // We're clear on point (1) because this is only called at the start of this + // binary (we know what the state of the world looks like) and we're mostly + // clear on point (2) because we'd only free it after everything is done + // anyway + unsafe { + git2_curl::register(handle); + } +} + +pub fn build_cli() -> App { + subcommand("build") + // subcommand aliases are handled in aliased_command() + // .alias("b") + .about("Compile a local package and all of its dependencies") + .arg(opt("quiet", "No output printed to stdout").short('q')) + .arg_package_spec( + "Package to build (see `cargo help pkgid`)", + "Build all packages in the workspace", + "Exclude packages from the build", + ) + .arg_jobs() + .arg_targets_all( + "Build only this package's library", + "Build only the specified binary", + "Build all binaries", + "Build only the specified example", + "Build all examples", + "Build only the specified test target", + "Build all tests", + "Build only the specified bench target", + "Build all benches", + "Build all targets", + ) + .arg_release("Build artifacts in release mode, with optimizations") + .arg_profile("Build artifacts with the specified profile") + .arg_features() + .arg_target_triple("Build for the target triple") + .arg_target_dir() + .arg( + opt( + "out-dir", + "Copy final artifacts to this directory (unstable)", + ) + .value_name("PATH"), + ) + .arg_manifest_path() + .arg_ignore_rust_version() + .arg_message_format() + .arg_build_plan() + .arg_unit_graph() + .arg_future_incompat_report() + .after_help("Run `cargo help build` for more detailed information.\n") +}