blob: b5ac0c997d1acfc3c40cdd1075c2b60f53640d1f [file] [log] [blame]
//! Code for building the standard library.
use crate::core::compiler::unit_dependencies::IsArtifact;
use crate::core::compiler::UnitInterner;
use crate::core::compiler::{CompileKind, CompileMode, RustcTargetData, Unit};
use crate::core::profiles::{Profiles, UnitFor};
use crate::core::resolver::features::{CliFeatures, FeaturesFor, ResolvedFeatures};
use crate::core::resolver::HasDevUnits;
use crate::core::{Dependency, PackageId, PackageSet, Resolve, SourceId, Workspace};
use crate::ops::{self, Packages};
use crate::util::errors::CargoResult;
use crate::Config;
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
use super::BuildConfig;
/// Parse the `-Zbuild-std` flag.
pub fn parse_unstable_flag(value: Option<&str>) -> Vec<String> {
// This is a temporary hack until there is a more principled way to
// declare dependencies in Cargo.toml.
let value = value.unwrap_or("std");
let mut crates: HashSet<&str> = value.split(',').collect();
if crates.contains("std") {
crates.insert("core");
crates.insert("alloc");
crates.insert("proc_macro");
crates.insert("panic_unwind");
crates.insert("compiler_builtins");
} else if crates.contains("core") {
crates.insert("compiler_builtins");
}
crates.into_iter().map(|s| s.to_string()).collect()
}
pub(crate) fn std_crates(config: &Config, units: Option<&[Unit]>) -> Option<Vec<String>> {
let crates = config.cli_unstable().build_std.as_ref()?.clone();
// Only build libtest if it looks like it is needed.
let mut crates = crates.clone();
// If we know what units we're building, we can filter for libtest depending on the jobs.
if let Some(units) = units {
if units
.iter()
.any(|unit| unit.mode.is_rustc_test() && unit.target.harness())
{
// Only build libtest when libstd is built (libtest depends on libstd)
if crates.iter().any(|c| c == "std") && !crates.iter().any(|c| c == "test") {
crates.push("test".to_string());
}
}
} else {
// We don't know what jobs are going to be run, so download libtest just in case.
if !crates.iter().any(|c| c == "test") {
crates.push("test".to_string())
}
}
Some(crates)
}
/// Resolve the standard library dependencies.
pub fn resolve_std<'cfg>(
ws: &Workspace<'cfg>,
target_data: &RustcTargetData<'cfg>,
build_config: &BuildConfig,
crates: &[String],
) -> CargoResult<(PackageSet<'cfg>, Resolve, ResolvedFeatures)> {
if build_config.build_plan {
ws.config()
.shell()
.warn("-Zbuild-std does not currently fully support --build-plan")?;
}
let src_path = detect_sysroot_src_path(target_data)?;
let to_patch = [
"rustc-std-workspace-core",
"rustc-std-workspace-alloc",
"rustc-std-workspace-std",
];
let patches = to_patch
.iter()
.map(|&name| {
let source_path = SourceId::for_path(&src_path.join("library").join(name))?;
let dep = Dependency::parse(name, None, source_path)?;
Ok(dep)
})
.collect::<CargoResult<Vec<_>>>()?;
let crates_io_url = crate::sources::CRATES_IO_INDEX.parse().unwrap();
let patch = HashMap::from([(crates_io_url, patches)]);
let members = vec![
String::from("library/std"),
String::from("library/core"),
String::from("library/alloc"),
String::from("library/test"),
];
let ws_config = crate::core::WorkspaceConfig::Root(crate::core::WorkspaceRootConfig::new(
&src_path,
&Some(members),
/*default_members*/ &None,
/*exclude*/ &None,
/*inheritable*/ &None,
/*custom_metadata*/ &None,
));
let virtual_manifest = crate::core::VirtualManifest::new(
/*replace*/ Vec::new(),
patch,
ws_config,
/*profiles*/ None,
crate::core::Features::default(),
None,
);
let config = ws.config();
// This is a delicate hack. In order for features to resolve correctly,
// the resolver needs to run a specific "current" member of the workspace.
// Thus, in order to set the features for `std`, we need to set `libtest`
// to be the "current" member. `libtest` is the root, and all other
// standard library crates are dependencies from there. Since none of the
// other crates need to alter their features, this should be fine, for
// now. Perhaps in the future features will be decoupled from the resolver
// and it will be easier to control feature selection.
let current_manifest = src_path.join("library/test/Cargo.toml");
// TODO: Consider doing something to enforce --locked? Or to prevent the
// lock file from being written, such as setting ephemeral.
let mut std_ws = Workspace::new_virtual(src_path, current_manifest, virtual_manifest, config)?;
// Don't require optional dependencies in this workspace, aka std's own
// `[dev-dependencies]`. No need for us to generate a `Resolve` which has
// those included because we'll never use them anyway.
std_ws.set_require_optional_deps(false);
// `test` is not in the default set because it is optional, but it needs
// to be part of the resolve in case we do need it.
let mut spec_pkgs = Vec::from(crates);
spec_pkgs.push("test".to_string());
let spec = Packages::Packages(spec_pkgs);
let specs = spec.to_package_id_specs(&std_ws)?;
let features = match &config.cli_unstable().build_std_features {
Some(list) => list.clone(),
None => vec![
"panic-unwind".to_string(),
"backtrace".to_string(),
"default".to_string(),
],
};
let cli_features = CliFeatures::from_command_line(
&features, /*all_features*/ false, /*uses_default_features*/ false,
)?;
let resolve = ops::resolve_ws_with_opts(
&std_ws,
target_data,
&build_config.requested_kinds,
&cli_features,
&specs,
HasDevUnits::No,
crate::core::resolver::features::ForceAllTargets::No,
)?;
Ok((
resolve.pkg_set,
resolve.targeted_resolve,
resolve.resolved_features,
))
}
/// Generate a list of root `Unit`s for the standard library.
///
/// The given slice of crate names is the root set.
pub fn generate_std_roots(
crates: &[String],
std_resolve: &Resolve,
std_features: &ResolvedFeatures,
kinds: &[CompileKind],
package_set: &PackageSet<'_>,
interner: &UnitInterner,
profiles: &Profiles,
) -> CargoResult<HashMap<CompileKind, Vec<Unit>>> {
// Generate the root Units for the standard library.
let std_ids = crates
.iter()
.map(|crate_name| std_resolve.query(crate_name))
.collect::<CargoResult<Vec<PackageId>>>()?;
// Convert PackageId to Package.
let std_pkgs = package_set.get_many(std_ids)?;
// Generate a map of Units for each kind requested.
let mut ret = HashMap::new();
for pkg in std_pkgs {
let lib = pkg
.targets()
.iter()
.find(|t| t.is_lib())
.expect("std has a lib");
// I don't think we need to bother with Check here, the difference
// in time is minimal, and the difference in caching is
// significant.
let mode = CompileMode::Build;
let features = std_features.activated_features(pkg.package_id(), FeaturesFor::NormalOrDev);
for kind in kinds {
let list = ret.entry(*kind).or_insert_with(Vec::new);
let unit_for = UnitFor::new_normal(*kind);
let profile = profiles.get_profile(
pkg.package_id(),
/*is_member*/ false,
/*is_local*/ false,
unit_for,
*kind,
);
list.push(interner.intern(
pkg,
lib,
profile,
*kind,
mode,
features.clone(),
/*is_std*/ true,
/*dep_hash*/ 0,
IsArtifact::No,
None,
));
}
}
Ok(ret)
}
fn detect_sysroot_src_path(target_data: &RustcTargetData<'_>) -> CargoResult<PathBuf> {
if let Some(s) = target_data.config.get_env_os("__CARGO_TESTS_ONLY_SRC_ROOT") {
return Ok(s.into());
}
// NOTE: This is temporary until we figure out how to acquire the source.
let src_path = target_data
.info(CompileKind::Host)
.sysroot
.join("lib")
.join("rustlib")
.join("src")
.join("rust");
let lock = src_path.join("Cargo.lock");
if !lock.exists() {
let msg = format!(
"{:?} does not exist, unable to build with the standard \
library, try:\n rustup component add rust-src",
lock
);
match target_data.config.get_env("RUSTUP_TOOLCHAIN") {
Ok(rustup_toolchain) => {
anyhow::bail!("{} --toolchain {}", msg, rustup_toolchain);
}
Err(_) => {
anyhow::bail!(msg);
}
}
}
Ok(src_path)
}