| //! The entry point for starting the compilation process for commands like |
| //! `build`, `test`, `doc`, `rustc`, etc. |
| //! |
| //! The [`compile`] function will do all the work to compile a workspace. A |
| //! rough outline is: |
| //! |
| //! 1. Resolve the dependency graph (see [`ops::resolve`]). |
| //! 2. Download any packages needed (see [`PackageSet`]). |
| //! 3. Generate a list of top-level "units" of work for the targets the user |
| //! requested on the command-line. Each [`Unit`] corresponds to a compiler |
| //! invocation. This is done in this module ([`UnitGenerator::generate_root_units`]). |
| //! 4. Starting from the root [`Unit`]s, generate the [`UnitGraph`] by walking the dependency graph |
| //! from the resolver. See also [`unit_dependencies`]. |
| //! 5. Construct the [`BuildContext`] with all of the information collected so |
| //! far. This is the end of the "front end" of compilation. |
| //! 6. Create a [`BuildRunner`] which coordinates the compilation process |
| //! and will perform the following steps: |
| //! 1. Prepare the `target` directory (see [`Layout`]). |
| //! 2. Create a [`JobQueue`]. The queue checks the |
| //! fingerprint of each `Unit` to determine if it should run or be |
| //! skipped. |
| //! 3. Execute the queue via [`drain_the_queue`]. Each leaf in the queue's dependency graph is |
| //! executed, and then removed from the graph when finished. This repeats until the queue is |
| //! empty. Note that this is the only point in cargo that currently uses threads. |
| //! 7. The result of the compilation is stored in the [`Compilation`] struct. This can be used for |
| //! various things, such as running tests after the compilation has finished. |
| //! |
| //! **Note**: "target" inside this module generally refers to ["Cargo Target"], |
| //! which corresponds to artifact that will be built in a package. Not to be |
| //! confused with target-triple or target architecture. |
| //! |
| //! [`unit_dependencies`]: crate::compiler::unit_dependencies |
| //! [`Layout`]: crate::compiler::Layout |
| //! [`JobQueue`]: crate::compiler::job_queue |
| //! [`drain_the_queue`]: crate::compiler::job_queue |
| //! ["Cargo Target"]: https://doc.rust-lang.org/nightly/cargo/reference/cargo-targets.html |
| |
| use crate::util::data_structures::{HashMap, HashSet}; |
| use std::hash::{Hash, Hasher}; |
| use std::sync::Arc; |
| |
| use crate::compiler::UserIntent; |
| use crate::compiler::unit_dependencies::build_unit_dependencies; |
| use crate::compiler::unit_graph::{self, UnitDep, UnitGraph}; |
| use crate::compiler::{BuildConfig, BuildContext, BuildRunner, Compilation}; |
| use crate::compiler::{CompileKind, CompileTarget, RustcTargetData, Unit}; |
| use crate::compiler::{CrateType, TargetInfo, apply_env_config, standard_lib}; |
| use crate::compiler::{DefaultExecutor, Executor, UnitInterner}; |
| use crate::compiler::{DepKindSet, UnitIndex}; |
| use crate::context::{GlobalContext, WarningHandling}; |
| use crate::drop_println; |
| use crate::ops; |
| use crate::ops::resolve::{SpecsAndResolvedFeatures, WorkspaceResolve}; |
| use crate::resolver::features::{self, CliFeatures, FeaturesFor}; |
| use crate::resolver::{ForceAllTargets, HasDevUnits, Resolve}; |
| use crate::util::BuildLogger; |
| use crate::util::interning::InternedString; |
| use crate::util::log_message::LogMessage; |
| use crate::util::{CargoResult, StableHasher}; |
| use crate::workspace::profiles::Profiles; |
| use crate::workspace::{PackageId, PackageSet, SourceId, TargetKind, Workspace}; |
| |
| mod compile_filter; |
| use cargo_util_terminal::report::{Group, Level, Origin}; |
| pub use compile_filter::{CompileFilter, FilterRule, LibRule}; |
| |
| pub(super) mod unit_generator; |
| use itertools::Itertools as _; |
| use unit_generator::UnitGenerator; |
| |
| mod packages; |
| |
| pub use packages::Packages; |
| |
| /// Contains information about how a package should be compiled. |
| /// |
| /// Note on distinction between `CompileOptions` and [`BuildConfig`]: |
| /// `BuildConfig` contains values that need to be retained after |
| /// [`BuildContext`] is created. The other fields are no longer necessary. Think |
| /// of it as `CompileOptions` are high-level settings requested on the |
| /// command-line, and `BuildConfig` are low-level settings for actually |
| /// driving `rustc`. |
| #[derive(Debug, Clone)] |
| pub struct CompileOptions { |
| /// Configuration information for a rustc build |
| pub build_config: BuildConfig, |
| /// Feature flags requested by the user. |
| pub cli_features: CliFeatures, |
| /// A set of packages to build. |
| pub spec: Packages, |
| /// Filter to apply to the root package to select which targets will be |
| /// built. |
| pub filter: CompileFilter, |
| /// Extra arguments to be passed to rustdoc (single target only) |
| pub target_rustdoc_args: Option<Vec<String>>, |
| /// The specified target will be compiled with all the available arguments, |
| /// note that this only accounts for the *final* invocation of rustc |
| pub target_rustc_args: Option<Vec<String>>, |
| /// Crate types to be passed to rustc (single target only) |
| pub target_rustc_crate_types: Option<Vec<String>>, |
| /// Whether the `--document-private-items` flags was specified and should |
| /// be forwarded to `rustdoc`. |
| pub rustdoc_document_private_items: bool, |
| /// Whether the build process should check the minimum Rust version |
| /// defined in the cargo metadata for a crate. |
| pub honor_rust_version: Option<bool>, |
| } |
| |
| impl CompileOptions { |
| pub fn new(gctx: &GlobalContext, intent: UserIntent) -> CargoResult<CompileOptions> { |
| let jobs = None; |
| let keep_going = false; |
| Ok(CompileOptions { |
| build_config: BuildConfig::new(gctx, jobs, keep_going, &[], intent)?, |
| cli_features: CliFeatures::new_all(false), |
| spec: ops::Packages::Packages(Vec::new()), |
| filter: CompileFilter::Default { |
| required_features_filterable: false, |
| }, |
| target_rustdoc_args: None, |
| target_rustc_args: None, |
| target_rustc_crate_types: None, |
| rustdoc_document_private_items: false, |
| honor_rust_version: None, |
| }) |
| } |
| } |
| |
| /// Compiles! |
| /// |
| /// This uses the [`DefaultExecutor`]. To use a custom [`Executor`], see [`compile_with_exec`]. |
| pub fn compile<'a>(ws: &Workspace<'a>, options: &CompileOptions) -> CargoResult<Compilation<'a>> { |
| let exec: Arc<dyn Executor> = Arc::new(DefaultExecutor); |
| compile_with_exec(ws, options, &exec) |
| } |
| |
| /// Like [`compile`] but allows specifying a custom [`Executor`] |
| /// that will be able to intercept build calls and add custom logic. |
| /// |
| /// [`compile`] uses [`DefaultExecutor`] which just passes calls through. |
| pub fn compile_with_exec<'a>( |
| ws: &Workspace<'a>, |
| options: &CompileOptions, |
| exec: &Arc<dyn Executor>, |
| ) -> CargoResult<Compilation<'a>> { |
| let parse_pass_output = crate::diagnostics::passes::emit_parse_diagnostics( |
| ws, |
| crate::diagnostics::rules::PARSE_PASS_RULES, |
| )?; |
| let compilation = compile_ws(ws, options, exec)?; |
| if ws.gctx().warning_handling()? == WarningHandling::Deny |
| && (compilation.lint_warning_count + parse_pass_output.lint_warning_count) > 0 |
| { |
| anyhow::bail!("warnings are denied by `build.warnings` configuration") |
| } |
| Ok(compilation) |
| } |
| |
| /// Like [`compile_with_exec`] but without warnings from manifest parsing. |
| #[tracing::instrument(skip_all)] |
| fn compile_ws<'a>( |
| ws: &Workspace<'a>, |
| options: &CompileOptions, |
| exec: &Arc<dyn Executor>, |
| ) -> CargoResult<Compilation<'a>> { |
| let interner = UnitInterner::new(); |
| let logger = BuildLogger::maybe_new(ws, &options.build_config)?; |
| |
| if let Some(ref logger) = logger { |
| let rustc = ws.gctx().load_global_rustc(Some(ws))?; |
| let num_cpus = std::thread::available_parallelism() |
| .ok() |
| .map(|x| x.get() as u64); |
| logger.log(LogMessage::BuildStarted { |
| command: std::env::args_os() |
| .map(|arg| arg.to_string_lossy().into_owned()) |
| .collect(), |
| cwd: ws.gctx().cwd().to_path_buf(), |
| host: rustc.host.to_string(), |
| jobs: options.build_config.jobs, |
| num_cpus, |
| profile: options.build_config.requested_profile.to_string(), |
| rustc_version: rustc.version.to_string(), |
| rustc_version_verbose: rustc.verbose_version.clone(), |
| target_dir: ws.target_dir().as_path_unlocked().to_path_buf(), |
| workspace_root: ws.root().to_path_buf(), |
| }); |
| } |
| |
| let bcx = create_bcx(ws, options, &interner, logger.as_ref())?; |
| |
| if options.build_config.unit_graph { |
| unit_graph::emit_serialized_unit_graph(&bcx.roots, &bcx.unit_graph, ws.gctx())?; |
| return Compilation::new(&bcx); |
| } |
| crate::workspace::gc::auto_gc(bcx.gctx); |
| let build_runner = BuildRunner::new(&bcx)?; |
| if options.build_config.dry_run { |
| build_runner.dry_run() |
| } else { |
| build_runner.compile(exec) |
| } |
| } |
| |
| /// Executes `rustc --print <VALUE>`. |
| /// |
| /// * `print_opt_value` is the VALUE passed through. |
| pub fn print<'a>( |
| ws: &Workspace<'a>, |
| options: &CompileOptions, |
| print_opt_value: &str, |
| ) -> CargoResult<()> { |
| let CompileOptions { |
| ref build_config, |
| ref target_rustc_args, |
| .. |
| } = *options; |
| let gctx = ws.gctx(); |
| let rustc = gctx.load_global_rustc(Some(ws))?; |
| for (index, kind) in build_config.requested_kinds.iter().enumerate() { |
| if index != 0 { |
| drop_println!(gctx); |
| } |
| let target_info = TargetInfo::new(gctx, &build_config.requested_kinds, &rustc, *kind)?; |
| let mut process = rustc.process(); |
| apply_env_config(gctx, &mut process)?; |
| process.args(&target_info.rustflags); |
| if let Some(args) = target_rustc_args { |
| process.args(args); |
| } |
| kind.add_target_arg(&mut process); |
| process.arg("--print").arg(print_opt_value); |
| process.exec()?; |
| } |
| Ok(()) |
| } |
| |
| /// Prepares all required information for the actual compilation. |
| /// |
| /// For how it works and what data it collects, |
| /// please see the [module-level documentation](self). |
| #[tracing::instrument(skip_all)] |
| pub fn create_bcx<'a, 'gctx>( |
| ws: &'a Workspace<'gctx>, |
| options: &'a CompileOptions, |
| interner: &'a UnitInterner, |
| logger: Option<&'a BuildLogger>, |
| ) -> CargoResult<BuildContext<'a, 'gctx>> { |
| let CompileOptions { |
| ref build_config, |
| ref spec, |
| ref cli_features, |
| ref filter, |
| ref target_rustdoc_args, |
| ref target_rustc_args, |
| ref target_rustc_crate_types, |
| rustdoc_document_private_items, |
| honor_rust_version, |
| } = *options; |
| let gctx = ws.gctx(); |
| |
| // Perform some pre-flight validation. |
| match build_config.intent { |
| UserIntent::Test | UserIntent::Build | UserIntent::Check { .. } | UserIntent::Bench => { |
| if ws.gctx().get_env("RUST_FLAGS").is_ok() { |
| gctx.shell().print_report( |
| &[Level::WARNING |
| .secondary_title("ignoring environment variable `RUST_FLAGS`") |
| .element(Level::HELP.message("rust flags are passed via `RUSTFLAGS`"))], |
| false, |
| )?; |
| } |
| } |
| UserIntent::Doc { .. } | UserIntent::Doctest => { |
| if ws.gctx().get_env("RUSTDOC_FLAGS").is_ok() { |
| gctx.shell().print_report( |
| &[Level::WARNING |
| .secondary_title("ignoring environment variable `RUSTDOC_FLAGS`") |
| .element( |
| Level::HELP.message("rustdoc flags are passed via `RUSTDOCFLAGS`"), |
| )], |
| false, |
| )?; |
| } |
| } |
| } |
| gctx.validate_term_config()?; |
| |
| let mut target_data = RustcTargetData::new(ws, &build_config.requested_kinds)?; |
| |
| let specs = spec.to_package_id_specs(ws)?; |
| let has_dev_units = { |
| // Rustdoc itself doesn't need dev-dependencies. But to scrape examples from packages in the |
| // workspace, if any of those packages need dev-dependencies, then we need include dev-dependencies |
| // to scrape those packages. |
| let any_pkg_has_scrape_enabled = ws |
| .members_with_features(&specs, cli_features)? |
| .iter() |
| .any(|(pkg, _)| { |
| pkg.targets() |
| .iter() |
| .any(|target| target.is_example() && target.doc_scrape_examples().is_enabled()) |
| }); |
| |
| if filter.need_dev_deps(build_config.intent) |
| || (build_config.intent.is_doc() && any_pkg_has_scrape_enabled) |
| { |
| HasDevUnits::Yes |
| } else { |
| HasDevUnits::No |
| } |
| }; |
| let dry_run = false; |
| |
| if let Some(logger) = logger { |
| let elapsed = ws.gctx().invocation_instant().elapsed().as_secs_f64(); |
| logger.log(LogMessage::ResolutionStarted { elapsed }); |
| } |
| |
| let resolve = ops::resolve_ws_with_opts( |
| ws, |
| &mut target_data, |
| &build_config.requested_kinds, |
| cli_features, |
| &specs, |
| has_dev_units, |
| ForceAllTargets::No, |
| dry_run, |
| )?; |
| let WorkspaceResolve { |
| mut pkg_set, |
| workspace_resolve, |
| targeted_resolve: resolve, |
| specs_and_features, |
| } = resolve; |
| |
| if let Some(logger) = logger { |
| let elapsed = ws.gctx().invocation_instant().elapsed().as_secs_f64(); |
| logger.log(LogMessage::ResolutionFinished { elapsed }); |
| } |
| |
| let std_resolve_features = if let Some(crates) = &gctx.cli_unstable().build_std { |
| let (std_package_set, std_resolve, std_features) = standard_lib::resolve_std( |
| ws, |
| &mut target_data, |
| &build_config, |
| crates, |
| &build_config.requested_kinds, |
| )?; |
| pkg_set.add_set(std_package_set); |
| Some((std_resolve, std_features)) |
| } else { |
| None |
| }; |
| |
| // Find the packages in the resolver that the user wants to build (those |
| // passed in with `-p` or the defaults from the workspace), and convert |
| // Vec<PackageIdSpec> to a Vec<PackageId>. |
| let to_build_ids = resolve.specs_to_ids(&specs)?; |
| // Now get the `Package` for each `PackageId`. This may trigger a download |
| // if the user specified `-p` for a dependency that is not downloaded. |
| // Dependencies will be downloaded during build_unit_dependencies. |
| let mut to_builds = pkg_set.get_many(to_build_ids)?; |
| |
| // The ordering here affects some error messages coming out of cargo, so |
| // let's be test and CLI friendly by always printing in the same order if |
| // there's an error. |
| to_builds.sort_by_key(|p| p.package_id()); |
| |
| for pkg in to_builds.iter() { |
| pkg.manifest().print_teapot(gctx); |
| |
| if build_config.intent.is_any_test() |
| && !ws.is_member(pkg) |
| && pkg.dependencies().iter().any(|dep| !dep.is_transitive()) |
| { |
| anyhow::bail!( |
| "package `{}` cannot be tested because it requires dev-dependencies \ |
| and is not a member of the workspace", |
| pkg.name() |
| ); |
| } |
| } |
| |
| let (extra_args, extra_args_name) = match (target_rustc_args, target_rustdoc_args) { |
| (Some(args), _) => (Some(args.clone()), "rustc"), |
| (_, Some(args)) => (Some(args.clone()), "rustdoc"), |
| _ => (None, ""), |
| }; |
| |
| if extra_args.is_some() && to_builds.len() != 1 { |
| panic!( |
| "`{}` should not accept multiple `-p` flags", |
| extra_args_name |
| ); |
| } |
| |
| let profiles = Profiles::new(ws, build_config.requested_profile)?; |
| profiles.validate_packages( |
| ws.profiles(), |
| &mut gctx.shell(), |
| workspace_resolve.as_ref().unwrap_or(&resolve), |
| )?; |
| |
| // If `--target` has not been specified, then the unit graph is built |
| // assuming `--target $HOST` was specified. See |
| // `rebuild_unit_graph_shared` for more on why this is done. |
| let explicit_host_kind = CompileKind::Target(CompileTarget::new( |
| &target_data.rustc.host, |
| gctx.cli_unstable().json_target_spec, |
| )?); |
| let explicit_host_kinds: Vec<_> = build_config |
| .requested_kinds |
| .iter() |
| .map(|kind| match kind { |
| CompileKind::Host => explicit_host_kind, |
| CompileKind::Target(t) => CompileKind::Target(*t), |
| }) |
| .collect(); |
| |
| let mut root_units = Vec::new(); |
| let mut unit_graph = HashMap::default(); |
| let mut scrape_units = Vec::new(); |
| |
| if let Some(logger) = logger { |
| let elapsed = ws.gctx().invocation_instant().elapsed().as_secs_f64(); |
| logger.log(LogMessage::UnitGraphStarted { elapsed }); |
| } |
| |
| let mut selected_dep_kinds = DepKindSet::default(); |
| for SpecsAndResolvedFeatures { |
| specs, |
| resolved_features, |
| } in &specs_and_features |
| { |
| // Passing `build_config.requested_kinds` instead of |
| // `explicit_host_kinds` here so that `generate_root_units` can do |
| // its own special handling of `CompileKind::Host`. It will |
| // internally replace the host kind by the `explicit_host_kind` |
| // before setting as a unit. |
| let spec_names = specs.iter().map(|spec| spec.name()).collect::<Vec<_>>(); |
| let packages = to_builds |
| .iter() |
| .filter(|package| spec_names.contains(&package.name().as_str())) |
| .cloned() |
| .collect::<Vec<_>>(); |
| let generator = UnitGenerator { |
| ws, |
| packages: &packages, |
| spec, |
| target_data: &target_data, |
| filter, |
| requested_kinds: &build_config.requested_kinds, |
| explicit_host_kind, |
| intent: build_config.intent, |
| resolve: &resolve, |
| workspace_resolve: &workspace_resolve, |
| resolved_features: &resolved_features, |
| package_set: &pkg_set, |
| profiles: &profiles, |
| interner, |
| has_dev_units, |
| }; |
| let (mut targeted_root_units, curr_selected_dep_kinds) = generator.generate_root_units()?; |
| // Should be fine as the loop iterate is independent of target selection |
| selected_dep_kinds = curr_selected_dep_kinds; |
| |
| if let Some(args) = target_rustc_crate_types { |
| override_rustc_crate_types(&mut targeted_root_units, args, interner)?; |
| } |
| |
| let should_scrape = |
| build_config.intent.is_doc() && gctx.cli_unstable().rustdoc_scrape_examples; |
| let targeted_scrape_units = if should_scrape { |
| generator.generate_scrape_units(&targeted_root_units)? |
| } else { |
| Vec::new() |
| }; |
| |
| let std_roots = if let Some(crates) = gctx.cli_unstable().build_std.as_ref() { |
| let (std_resolve, std_features) = std_resolve_features.as_ref().unwrap(); |
| standard_lib::generate_std_roots( |
| &crates, |
| &targeted_root_units, |
| std_resolve, |
| std_features, |
| &explicit_host_kinds, |
| &pkg_set, |
| interner, |
| &profiles, |
| &target_data, |
| )? |
| } else { |
| Default::default() |
| }; |
| |
| unit_graph.extend(build_unit_dependencies( |
| ws, |
| &pkg_set, |
| &resolve, |
| &resolved_features, |
| std_resolve_features.as_ref(), |
| &targeted_root_units, |
| &targeted_scrape_units, |
| &std_roots, |
| build_config.intent, |
| &target_data, |
| &profiles, |
| interner, |
| )?); |
| root_units.extend(targeted_root_units); |
| scrape_units.extend(targeted_scrape_units); |
| } |
| |
| // TODO: In theory, Cargo should also dedupe the roots, but I'm uncertain |
| // what heuristics to use in that case. |
| if build_config.intent.wants_deps_docs() { |
| remove_duplicate_doc(build_config, &root_units, &mut unit_graph); |
| } |
| |
| let host_kind_requested = build_config |
| .requested_kinds |
| .iter() |
| .any(CompileKind::is_host); |
| // Rebuild the unit graph, replacing the explicit host targets with |
| // CompileKind::Host, removing `artifact_target_for_features` and merging any dependencies |
| // shared with build and artifact dependencies. |
| // |
| // NOTE: after this point, all units and the unit graph must be immutable. |
| let (root_units, scrape_units, unit_graph) = rebuild_unit_graph_shared( |
| interner, |
| unit_graph, |
| &root_units, |
| &scrape_units, |
| host_kind_requested.then_some(explicit_host_kind), |
| build_config.compile_time_deps_only, |
| ); |
| |
| let units: Vec<_> = unit_graph.keys().sorted().collect(); |
| let unit_to_index: HashMap<_, _> = units |
| .iter() |
| .enumerate() |
| .map(|(i, &unit)| (unit.clone(), UnitIndex(i as u64))) |
| .collect(); |
| |
| if let Some(logger) = logger { |
| let root_unit_indexes: HashSet<_> = |
| root_units.iter().map(|unit| unit_to_index[&unit]).collect(); |
| |
| for (index, unit) in units.into_iter().enumerate() { |
| let index = UnitIndex(index as u64); |
| let dependencies = unit_graph |
| .get(unit) |
| .map(|deps| { |
| deps.iter() |
| .filter_map(|dep| unit_to_index.get(&dep.unit).copied()) |
| .collect() |
| }) |
| .unwrap_or_default(); |
| logger.log(LogMessage::UnitRegistered { |
| package_id: unit.pkg.package_id().to_spec(), |
| target: (&unit.target).into(), |
| mode: unit.mode, |
| platform: target_data.short_name(&unit.kind).to_owned(), |
| index, |
| features: unit |
| .features |
| .iter() |
| .map(|s| s.as_str().to_owned()) |
| .collect(), |
| requested: root_unit_indexes.contains(&index), |
| dependencies, |
| }); |
| } |
| let elapsed = ws.gctx().invocation_instant().elapsed().as_secs_f64(); |
| logger.log(LogMessage::UnitGraphFinished { elapsed }); |
| } |
| |
| let mut extra_compiler_args = HashMap::default(); |
| if let Some(args) = extra_args { |
| if root_units.len() != 1 { |
| anyhow::bail!( |
| "extra arguments to `{}` can only be passed to one \ |
| target, consider filtering\nthe package by passing, \ |
| e.g., `--lib` or `--bin NAME` to specify a single target", |
| extra_args_name |
| ); |
| } |
| extra_compiler_args.insert(root_units[0].clone(), args); |
| } |
| |
| for unit in root_units |
| .iter() |
| .filter(|unit| unit.mode.is_doc() || unit.mode.is_doc_test()) |
| .filter(|unit| rustdoc_document_private_items || unit.target.is_bin()) |
| { |
| // Add `--document-private-items` rustdoc flag if requested or if |
| // the target is a binary. Binary crates get their private items |
| // documented by default. |
| let mut args = vec!["--document-private-items".into()]; |
| if unit.target.is_bin() { |
| // This warning only makes sense if it's possible to document private items |
| // sometimes and ignore them at other times. But cargo consistently passes |
| // `--document-private-items`, so the warning isn't useful. |
| args.push("-Arustdoc::private-intra-doc-links".into()); |
| } |
| extra_compiler_args |
| .entry(unit.clone()) |
| .or_default() |
| .extend(args); |
| } |
| |
| // Validate target src path for each root unit |
| let mut error_count: usize = 0; |
| for unit in &root_units { |
| if let Some(target_src_path) = unit.target.src_path().path() { |
| validate_target_path_as_source_file( |
| gctx, |
| target_src_path, |
| unit.target.name(), |
| unit.target.kind(), |
| unit.pkg.manifest_path(), |
| &mut error_count, |
| )? |
| } |
| } |
| if error_count > 0 { |
| let plural: &str = if error_count > 1 { "s" } else { "" }; |
| anyhow::bail!( |
| "could not compile due to {error_count} previous target resolution error{plural}" |
| ); |
| } |
| |
| if honor_rust_version.unwrap_or(true) { |
| let rustc_version = target_data.rustc.version.clone().into(); |
| |
| let mut incompatible = Vec::new(); |
| let mut local_incompatible = false; |
| for unit in unit_graph.keys() { |
| let Some(pkg_msrv) = unit.pkg.rust_version() else { |
| continue; |
| }; |
| |
| if pkg_msrv.is_compatible_with(&rustc_version) { |
| continue; |
| } |
| |
| local_incompatible |= unit.is_local(); |
| incompatible.push((unit, pkg_msrv)); |
| } |
| if !incompatible.is_empty() { |
| use std::fmt::Write as _; |
| |
| let plural = if incompatible.len() == 1 { "" } else { "s" }; |
| let mut message = format!( |
| "rustc {rustc_version} is not supported by the following package{plural}:\n" |
| ); |
| incompatible.sort_by_key(|(unit, _)| (unit.pkg.name(), unit.pkg.version())); |
| for (unit, msrv) in incompatible { |
| let name = &unit.pkg.name(); |
| let version = &unit.pkg.version(); |
| writeln!(&mut message, " {name}@{version} requires rustc {msrv}").unwrap(); |
| } |
| if ws.is_ephemeral() { |
| if ws.ignore_lock() { |
| writeln!( |
| &mut message, |
| "Try re-running `cargo install` with `--locked`" |
| ) |
| .unwrap(); |
| } |
| } else if !local_incompatible { |
| writeln!( |
| &mut message, |
| "Either upgrade rustc or select compatible dependency versions with |
| `cargo update <name>@<current-ver> --precise <compatible-ver>` |
| where `<compatible-ver>` is the latest version supporting rustc {rustc_version}", |
| ) |
| .unwrap(); |
| } |
| return Err(anyhow::Error::msg(message)); |
| } |
| } |
| |
| let bcx = BuildContext::new( |
| ws, |
| logger, |
| pkg_set, |
| build_config, |
| selected_dep_kinds, |
| profiles, |
| extra_compiler_args, |
| target_data, |
| root_units, |
| unit_graph, |
| unit_to_index, |
| scrape_units, |
| )?; |
| |
| Ok(bcx) |
| } |
| |
| // Checks if a target path exists and is a source file, not a directory |
| fn validate_target_path_as_source_file( |
| gctx: &GlobalContext, |
| target_path: &std::path::Path, |
| target_name: &str, |
| target_kind: &TargetKind, |
| unit_manifest_path: &std::path::Path, |
| error_count: &mut usize, |
| ) -> CargoResult<()> { |
| if !target_path.exists() { |
| *error_count += 1; |
| |
| let err_msg = format!( |
| "can't find {} `{}` at path `{}`", |
| target_kind.description(), |
| target_name, |
| target_path.display() |
| ); |
| |
| let group = Group::with_title(Level::ERROR.primary_title(err_msg)).element(Origin::path( |
| unit_manifest_path.to_str().unwrap_or_default(), |
| )); |
| |
| gctx.shell().print_report(&[group], true)?; |
| } else if target_path.is_dir() { |
| *error_count += 1; |
| |
| // suggest setting the path to a likely entrypoint |
| let main_rs = target_path.join("main.rs"); |
| let lib_rs = target_path.join("lib.rs"); |
| |
| let suggested_files_opt = match target_kind { |
| TargetKind::Lib(_) => { |
| if lib_rs.exists() { |
| Some(format!("`{}`", lib_rs.display())) |
| } else { |
| None |
| } |
| } |
| TargetKind::Bin => { |
| if main_rs.exists() { |
| Some(format!("`{}`", main_rs.display())) |
| } else { |
| None |
| } |
| } |
| TargetKind::Test => { |
| if main_rs.exists() { |
| Some(format!("`{}`", main_rs.display())) |
| } else { |
| None |
| } |
| } |
| TargetKind::ExampleBin => { |
| if main_rs.exists() { |
| Some(format!("`{}`", main_rs.display())) |
| } else { |
| None |
| } |
| } |
| TargetKind::Bench => { |
| if main_rs.exists() { |
| Some(format!("`{}`", main_rs.display())) |
| } else { |
| None |
| } |
| } |
| TargetKind::ExampleLib(_) => { |
| if lib_rs.exists() { |
| Some(format!("`{}`", lib_rs.display())) |
| } else { |
| None |
| } |
| } |
| TargetKind::CustomBuild => None, |
| }; |
| |
| let err_msg = format!( |
| "path `{}` for {} `{}` is a directory, but a source file was expected.", |
| target_path.display(), |
| target_kind.description(), |
| target_name, |
| ); |
| let mut group = Group::with_title(Level::ERROR.primary_title(err_msg)).element( |
| Origin::path(unit_manifest_path.to_str().unwrap_or_default()), |
| ); |
| |
| if let Some(suggested_files) = suggested_files_opt { |
| group = group.element( |
| Level::HELP.message(format!("an entry point exists at {}", suggested_files)), |
| ); |
| } |
| |
| gctx.shell().print_report(&[group], true)?; |
| } |
| |
| Ok(()) |
| } |
| |
| /// This is used to rebuild the unit graph, sharing host dependencies if possible, |
| /// and applying other unit adjustments based on the whole graph. |
| /// |
| /// This will translate any unit's `CompileKind::Target(host)` to |
| /// `CompileKind::Host` if `to_host` is not `None` and the kind is equal to `to_host`. |
| /// This also handles generating the unit `dep_hash`, and merging shared units if possible. |
| /// |
| /// This is necessary because if normal dependencies used `CompileKind::Host`, |
| /// there would be no way to distinguish those units from build-dependency |
| /// units or artifact dependency units. |
| /// This can cause a problem if a shared normal/build/artifact dependency needs |
| /// to link to another dependency whose features differ based on whether or |
| /// not it is a normal, build or artifact dependency. If all units used |
| /// `CompileKind::Host`, then they would end up being identical, causing a |
| /// collision in the `UnitGraph`, and Cargo would end up randomly choosing one |
| /// value or the other. |
| /// |
| /// The solution is to keep normal, build and artifact dependencies separate when |
| /// building the unit graph, and then run this second pass which will try to |
| /// combine shared dependencies safely. By adding a hash of the dependencies |
| /// to the `Unit`, this allows the `CompileKind` to be changed back to `Host` |
| /// and `artifact_target_for_features` to be removed without fear of an unwanted |
| /// collision for build or artifact dependencies. |
| /// |
| /// This is also responsible for adjusting the `strip` profile option to |
| /// opportunistically strip if debug is 0 for all dependencies. This helps |
| /// remove debuginfo added by the standard library. |
| /// |
| /// This is also responsible for adjusting the `debug` setting for host |
| /// dependencies, turning off debug if the user has not explicitly enabled it, |
| /// and the unit is not shared with a target unit. |
| /// |
| /// This is also responsible for adjusting whether each unit should be compiled |
| /// or not regarding `--compile-time-deps` flag. |
| fn rebuild_unit_graph_shared( |
| interner: &UnitInterner, |
| unit_graph: UnitGraph, |
| roots: &[Unit], |
| scrape_units: &[Unit], |
| to_host: Option<CompileKind>, |
| compile_time_deps_only: bool, |
| ) -> (Vec<Unit>, Vec<Unit>, UnitGraph) { |
| let mut result = UnitGraph::default(); |
| // Map of the old unit to the new unit, used to avoid recursing into units |
| // that have already been computed to improve performance. |
| let mut memo = HashMap::default(); |
| let new_roots = roots |
| .iter() |
| .map(|root| { |
| traverse_and_share( |
| interner, |
| &mut memo, |
| &mut result, |
| &unit_graph, |
| root, |
| true, |
| false, |
| to_host, |
| compile_time_deps_only, |
| ) |
| }) |
| .collect(); |
| // If no unit in the unit graph ended up having scrape units attached as dependencies, |
| // then they won't have been discovered in traverse_and_share and hence won't be in |
| // memo. So we filter out missing scrape units. |
| let new_scrape_units = scrape_units |
| .iter() |
| .map(|unit| memo.get(unit).unwrap().clone()) |
| .collect(); |
| (new_roots, new_scrape_units, result) |
| } |
| |
| /// Recursive function for rebuilding the graph. |
| /// |
| /// This walks `unit_graph`, starting at the given `unit`. It inserts the new |
| /// units into `new_graph`, and returns a new updated version of the given |
| /// unit (`dep_hash` is filled in, and `kind` switched if necessary). |
| fn traverse_and_share( |
| interner: &UnitInterner, |
| memo: &mut HashMap<Unit, Unit>, |
| new_graph: &mut UnitGraph, |
| unit_graph: &UnitGraph, |
| unit: &Unit, |
| unit_is_root: bool, |
| unit_is_for_host: bool, |
| to_host: Option<CompileKind>, |
| compile_time_deps_only: bool, |
| ) -> Unit { |
| if let Some(new_unit) = memo.get(unit) { |
| // Already computed, no need to recompute. |
| return new_unit.clone(); |
| } |
| let mut dep_hash = StableHasher::new(); |
| let skip_non_compile_time_deps = compile_time_deps_only |
| && (!unit.target.is_compile_time_dependency() || |
| // Root unit is not a dependency unless other units are dependant |
| // to it. |
| unit_is_root); |
| let new_deps: Vec<_> = unit_graph[unit] |
| .iter() |
| .map(|dep| { |
| let new_dep_unit = traverse_and_share( |
| interner, |
| memo, |
| new_graph, |
| unit_graph, |
| &dep.unit, |
| false, |
| dep.unit_for.is_for_host(), |
| to_host, |
| // If we should compile the current unit, we should also compile |
| // its dependencies. And if not, we should compile compile time |
| // dependencies only. |
| skip_non_compile_time_deps, |
| ); |
| new_dep_unit.hash(&mut dep_hash); |
| UnitDep { |
| unit: new_dep_unit, |
| ..dep.clone() |
| } |
| }) |
| .collect(); |
| // Here, we have recursively traversed this unit's dependencies, and hashed them: we can |
| // finalize the dep hash. |
| let new_dep_hash = Hasher::finish(&dep_hash); |
| |
| // This is the key part of the sharing process: if the unit is a runtime dependency, whose |
| // target is the same as the host, we canonicalize the compile kind to `CompileKind::Host`. |
| // A possible host dependency counterpart to this unit would have that kind, and if such a unit |
| // exists in the current `unit_graph`, they will unify in the new unit graph map `new_graph`. |
| // The resulting unit graph will be optimized with less units, thanks to sharing these host |
| // dependencies. |
| let canonical_kind = match to_host { |
| Some(to_host) if to_host == unit.kind => CompileKind::Host, |
| _ => unit.kind, |
| }; |
| |
| let mut profile = unit.profile.clone(); |
| if profile.strip.is_deferred() { |
| // If strip was not manually set, and all dependencies of this unit together |
| // with this unit have debuginfo turned off, we enable debuginfo stripping. |
| // This will remove pre-existing debug symbols coming from the standard library. |
| if !profile.debuginfo.is_turned_on() |
| && new_deps |
| .iter() |
| .all(|dep| !dep.unit.profile.debuginfo.is_turned_on()) |
| { |
| profile.strip = profile.strip.strip_debuginfo(); |
| } |
| } |
| |
| // If this is a build dependency, and it's not shared with runtime dependencies, we can weaken |
| // its debuginfo level to optimize build times. We do nothing if it's an artifact dependency, |
| // as it and its debuginfo may end up embedded in the main program. |
| if unit_is_for_host |
| && to_host.is_some() |
| && profile.debuginfo.is_deferred() |
| && !unit.artifact.is_true() |
| { |
| // We create a "probe" test to see if a unit with the same explicit debuginfo level exists |
| // in the graph. This is the level we'd expect if it was set manually or the default value |
| // set by a profile for a runtime dependency: its canonical value. |
| let canonical_debuginfo = profile.debuginfo.finalize(); |
| let mut canonical_profile = profile.clone(); |
| canonical_profile.debuginfo = canonical_debuginfo; |
| let unit_probe = interner.intern( |
| &unit.pkg, |
| &unit.target, |
| canonical_profile, |
| to_host.unwrap(), |
| unit.mode, |
| unit.features.clone(), |
| unit.rustflags.clone(), |
| unit.rustdocflags.clone(), |
| unit.links_overrides.clone(), |
| unit.is_std, |
| unit.dep_hash, |
| unit.artifact, |
| unit.artifact_target_for_features, |
| unit.skip_non_compile_time_dep, |
| ); |
| |
| // We can now turn the deferred value into its actual final value. |
| profile.debuginfo = if unit_graph.contains_key(&unit_probe) { |
| // The unit is present in both build time and runtime subgraphs: we canonicalize its |
| // level to the other unit's, thus ensuring reuse between the two to optimize build times. |
| canonical_debuginfo |
| } else { |
| // The unit is only present in the build time subgraph, we can weaken its debuginfo |
| // level to optimize build times. |
| canonical_debuginfo.weaken() |
| } |
| } |
| |
| let new_unit = interner.intern( |
| &unit.pkg, |
| &unit.target, |
| profile, |
| canonical_kind, |
| unit.mode, |
| unit.features.clone(), |
| unit.rustflags.clone(), |
| unit.rustdocflags.clone(), |
| unit.links_overrides.clone(), |
| unit.is_std, |
| new_dep_hash, |
| unit.artifact, |
| // Since `dep_hash` is now filled in, there's no need to specify the artifact target |
| // for target-dependent feature resolution |
| None, |
| skip_non_compile_time_deps, |
| ); |
| if !unit_is_root || !compile_time_deps_only { |
| assert!(memo.insert(unit.clone(), new_unit.clone()).is_none()); |
| } |
| new_graph.entry(new_unit.clone()).or_insert(new_deps); |
| new_unit |
| } |
| |
| /// Removes duplicate `CompileMode::Doc` units that would cause problems with |
| /// filename collisions. |
| /// |
| /// Rustdoc only separates units by crate name in the file directory |
| /// structure. If any two units with the same crate name exist, this would |
| /// cause a filename collision, causing different rustdoc invocations to stomp |
| /// on one another's files. |
| /// |
| /// Unfortunately this does not remove all duplicates, as some of them are |
| /// either user error, or difficult to remove. Cases that I can think of: |
| /// |
| /// - Same target name in different packages. See the `collision_doc` test. |
| /// - Different sources. See `collision_doc_sources` test. |
| /// |
| /// Ideally this would not be necessary. |
| fn remove_duplicate_doc( |
| build_config: &BuildConfig, |
| root_units: &[Unit], |
| unit_graph: &mut UnitGraph, |
| ) { |
| // First, create a mapping of crate_name -> Unit so we can see where the |
| // duplicates are. |
| let mut all_docs: HashMap<String, Vec<Unit>> = HashMap::default(); |
| for unit in unit_graph.keys() { |
| if unit.mode.is_doc() { |
| all_docs |
| .entry(unit.target.crate_name()) |
| .or_default() |
| .push(unit.clone()); |
| } |
| } |
| // Keep track of units to remove so that they can be efficiently removed |
| // from the unit_deps. |
| let mut removed_units: HashSet<Unit> = HashSet::default(); |
| let mut remove = |units: Vec<Unit>, reason: &str, cb: &dyn Fn(&Unit) -> bool| -> Vec<Unit> { |
| let (to_remove, remaining_units): (Vec<Unit>, Vec<Unit>) = units |
| .into_iter() |
| .partition(|unit| cb(unit) && !root_units.contains(unit)); |
| for unit in to_remove { |
| tracing::debug!( |
| "removing duplicate doc due to {} for package {} target `{}`", |
| reason, |
| unit.pkg, |
| unit.target.name() |
| ); |
| unit_graph.remove(&unit); |
| removed_units.insert(unit); |
| } |
| remaining_units |
| }; |
| // Iterate over the duplicates and try to remove them from unit_graph. |
| for (_crate_name, mut units) in all_docs { |
| if units.len() == 1 { |
| continue; |
| } |
| // Prefer target over host if --target was not specified. |
| if build_config |
| .requested_kinds |
| .iter() |
| .all(CompileKind::is_host) |
| { |
| // Note these duplicates may not be real duplicates, since they |
| // might get merged in rebuild_unit_graph_shared. Either way, it |
| // shouldn't hurt to remove them early (although the report in the |
| // log might be confusing). |
| units = remove(units, "host/target merger", &|unit| unit.kind.is_host()); |
| if units.len() == 1 { |
| continue; |
| } |
| } |
| // Prefer newer versions over older. |
| let mut source_map: HashMap<(InternedString, SourceId, CompileKind), Vec<Unit>> = |
| HashMap::default(); |
| for unit in units { |
| let pkg_id = unit.pkg.package_id(); |
| // Note, this does not detect duplicates from different sources. |
| source_map |
| .entry((pkg_id.name(), pkg_id.source_id(), unit.kind)) |
| .or_default() |
| .push(unit); |
| } |
| let mut remaining_units = Vec::new(); |
| for (_key, mut units) in source_map { |
| if units.len() > 1 { |
| units.sort_by(|a, b| a.pkg.version().partial_cmp(b.pkg.version()).unwrap()); |
| // Remove any entries with version < newest. |
| let newest_version = units.last().unwrap().pkg.version().clone(); |
| let keep_units = remove(units, "older version", &|unit| { |
| unit.pkg.version() < &newest_version |
| }); |
| remaining_units.extend(keep_units); |
| } else { |
| remaining_units.extend(units); |
| } |
| } |
| if remaining_units.len() == 1 { |
| continue; |
| } |
| // Are there other heuristics to remove duplicates that would make |
| // sense? Maybe prefer path sources over all others? |
| } |
| // Also remove units from the unit_deps so there aren't any dangling edges. |
| for unit_deps in unit_graph.values_mut() { |
| unit_deps.retain(|unit_dep| !removed_units.contains(&unit_dep.unit)); |
| } |
| // Remove any orphan units that were detached from the graph. |
| let mut visited = HashSet::default(); |
| fn visit(unit: &Unit, graph: &UnitGraph, visited: &mut HashSet<Unit>) { |
| if !visited.insert(unit.clone()) { |
| return; |
| } |
| for dep in &graph[unit] { |
| visit(&dep.unit, graph, visited); |
| } |
| } |
| for unit in root_units { |
| visit(unit, unit_graph, &mut visited); |
| } |
| unit_graph.retain(|unit, _| visited.contains(unit)); |
| } |
| |
| /// Override crate types for given units. |
| /// |
| /// This is primarily used by `cargo rustc --crate-type`. |
| fn override_rustc_crate_types( |
| units: &mut [Unit], |
| args: &[String], |
| interner: &UnitInterner, |
| ) -> CargoResult<()> { |
| if units.len() != 1 { |
| anyhow::bail!( |
| "crate types to rustc can only be passed to one \ |
| target, consider filtering\nthe package by passing, \ |
| e.g., `--lib` or `--example` to specify a single target" |
| ); |
| } |
| |
| let unit = &units[0]; |
| let override_unit = |f: fn(Vec<CrateType>) -> TargetKind| { |
| let crate_types = args.iter().map(|s| s.into()).collect(); |
| let mut target = unit.target.clone(); |
| target.set_kind(f(crate_types)); |
| interner.intern( |
| &unit.pkg, |
| &target, |
| unit.profile.clone(), |
| unit.kind, |
| unit.mode, |
| unit.features.clone(), |
| unit.rustflags.clone(), |
| unit.rustdocflags.clone(), |
| unit.links_overrides.clone(), |
| unit.is_std, |
| unit.dep_hash, |
| unit.artifact, |
| unit.artifact_target_for_features, |
| unit.skip_non_compile_time_dep, |
| ) |
| }; |
| units[0] = match unit.target.kind() { |
| TargetKind::Lib(_) => override_unit(TargetKind::Lib), |
| TargetKind::ExampleLib(_) => override_unit(TargetKind::ExampleLib), |
| _ => { |
| anyhow::bail!( |
| "crate types can only be specified for libraries and example libraries.\n\ |
| Binaries, tests, and benchmarks are always the `bin` crate type" |
| ); |
| } |
| }; |
| |
| Ok(()) |
| } |
| |
| /// Gets all of the features enabled for a package, plus its dependencies' |
| /// features. |
| /// |
| /// Dependencies are added as `dep_name/feat_name` because `required-features` |
| /// wants to support that syntax. |
| pub fn resolve_all_features( |
| resolve_with_overrides: &Resolve, |
| resolved_features: &features::ResolvedFeatures, |
| package_set: &PackageSet<'_>, |
| package_id: PackageId, |
| has_dev_units: HasDevUnits, |
| requested_kinds: &[CompileKind], |
| target_data: &RustcTargetData<'_>, |
| force_all_targets: ForceAllTargets, |
| ) -> HashSet<String> { |
| let mut features: HashSet<String> = resolved_features |
| .activated_features(package_id, FeaturesFor::NormalOrDev) |
| .iter() |
| .map(|s| s.to_string()) |
| .collect(); |
| |
| // Include features enabled for use by dependencies so targets can also use them with the |
| // required-features field when deciding whether to be built or skipped. |
| let filtered_deps = PackageSet::filter_deps( |
| package_id, |
| resolve_with_overrides, |
| has_dev_units, |
| requested_kinds, |
| target_data, |
| force_all_targets, |
| ); |
| for (dep_id, deps) in filtered_deps { |
| let is_proc_macro = package_set |
| .get_one(dep_id) |
| .expect("packages downloaded") |
| .proc_macro(); |
| for dep in deps { |
| let features_for = FeaturesFor::from_for_host(is_proc_macro || dep.is_build()); |
| for feature in resolved_features |
| .activated_features_unverified(dep_id, features_for) |
| .unwrap_or_default() |
| { |
| features.insert(format!("{}/{}", dep.name_in_toml(), feature)); |
| } |
| } |
| } |
| |
| features |
| } |