use crate::core::Feature;
use crate::core::compiler::{CompileKind, CompileTarget, Unit};
use crate::core::dependency::Artifact;
use crate::core::resolver::features::FeaturesFor;
use crate::core::{
PackageId, PackageIdSpec, PackageIdSpecQuery, Resolve, Shell, Target, Workspace,
};
use crate::util::interning::InternedString;
use crate::util::toml::validate_profile;
use crate::util::{CargoResult, GlobalContext, closest_msg, context};
use anyhow::{Context as _, bail};
use cargo_util_schemas::manifest::TomlTrimPaths;
use cargo_util_schemas::manifest::TomlTrimPathsValue;
use cargo_util_schemas::manifest::{
ProfilePackageSpec, StringOrBool, TomlDebugInfo, TomlProfile, TomlProfiles,
};
use std::collections::{BTreeMap, HashMap, HashSet};
use std::hash::Hash;
use std::{cmp, fmt, hash};
#[derive(Clone, Debug)]
pub struct Profiles {
incremental: Option<bool>,
dir_names: HashMap<InternedString, InternedString>,
by_name: HashMap<InternedString, ProfileMaker>,
original_profiles: BTreeMap<InternedString, TomlProfile>,
requested_profile: InternedString,
rustc_host: InternedString,
}
impl Profiles {
pub fn new(ws: &Workspace<'_>, requested_profile: InternedString) -> CargoResult<Profiles> {
let gctx = ws.gctx();
let incremental = match gctx.get_env_os("CARGO_INCREMENTAL") {
Some(v) => Some(v == "1"),
None => gctx.build_config()?.incremental,
};
let mut profiles = merge_config_profiles(ws, requested_profile)?;
let rustc_host = ws.gctx().load_global_rustc(Some(ws))?.host;
let mut profile_makers = Profiles {
incremental,
dir_names: Self::predefined_dir_names(),
by_name: HashMap::new(),
original_profiles: profiles.clone(),
requested_profile,
rustc_host,
};
let trim_paths_enabled = ws.unstable_features().is_enabled(Feature::trim_paths())
|| gctx.cli_unstable().trim_paths;
Self::add_root_profiles(&mut profile_makers, &profiles, trim_paths_enabled);
use std::collections::btree_map::Entry;
for (predef_name, mut predef_prof) in Self::predefined_profiles().into_iter() {
match profiles.entry(predef_name.into()) {
Entry::Vacant(vac) => {
vac.insert(predef_prof);
}
Entry::Occupied(mut oc) => {
let r = oc.get_mut();
predef_prof.merge(r);
*r = predef_prof;
}
}
}
for (name, profile) in &profiles {
profile_makers.add_maker(*name, profile, &profiles)?;
}
profile_makers.get_profile_maker(&requested_profile)?;
Ok(profile_makers)
}
fn predefined_dir_names() -> HashMap<InternedString, InternedString> {
[
("dev".into(), "debug".into()),
("test".into(), "debug".into()),
("bench".into(), "release".into()),
]
.into()
}
fn add_root_profiles(
profile_makers: &mut Profiles,
profiles: &BTreeMap<InternedString, TomlProfile>,
trim_paths_enabled: bool,
) {
profile_makers.by_name.insert(
"dev".into(),
ProfileMaker::new(Profile::default_dev(), profiles.get("dev").cloned()),
);
profile_makers.by_name.insert(
"release".into(),
ProfileMaker::new(
Profile::default_release(trim_paths_enabled),
profiles.get("release").cloned(),
),
);
}
fn predefined_profiles() -> Vec<(&'static str, TomlProfile)> {
vec![
(
"bench",
TomlProfile {
inherits: Some(String::from("release")),
..TomlProfile::default()
},
),
(
"test",
TomlProfile {
inherits: Some(String::from("dev")),
..TomlProfile::default()
},
),
(
"doc",
TomlProfile {
inherits: Some(String::from("dev")),
..TomlProfile::default()
},
),
]
}
fn add_maker(
&mut self,
name: InternedString,
profile: &TomlProfile,
profiles: &BTreeMap<InternedString, TomlProfile>,
) -> CargoResult<()> {
match &profile.dir_name {
None => {}
Some(dir_name) => {
self.dir_names.insert(name, dir_name.into());
}
}
if name == "dev" || name == "release" {
if profile.inherits.is_some() {
bail!(
"`inherits` must not be specified in root profile `{}`",
name
);
}
return Ok(());
}
let mut set = HashSet::new();
set.insert(name);
let maker = self.process_chain(name, profile, &mut set, profiles)?;
self.by_name.insert(name, maker);
Ok(())
}
fn process_chain(
&mut self,
name: InternedString,
profile: &TomlProfile,
set: &mut HashSet<InternedString>,
profiles: &BTreeMap<InternedString, TomlProfile>,
) -> CargoResult<ProfileMaker> {
let mut maker = match &profile.inherits {
Some(inherits_name) if inherits_name == "dev" || inherits_name == "release" => {
self.get_profile_maker(&inherits_name).unwrap().clone()
}
Some(inherits_name) => {
let inherits_name = inherits_name.into();
if !set.insert(inherits_name) {
bail!(
"profile inheritance loop detected with profile `{}` inheriting `{}`",
name,
inherits_name
);
}
match profiles.get(&inherits_name) {
None => {
bail!(
"profile `{}` inherits from `{}`, but that profile is not defined",
name,
inherits_name
);
}
Some(parent) => self.process_chain(inherits_name, parent, set, profiles)?,
}
}
None => {
bail!(
"profile `{}` is missing an `inherits` directive \
(`inherits` is required for all profiles except `dev` or `release`)",
name
);
}
};
match &mut maker.toml {
Some(toml) => toml.merge(profile),
None => maker.toml = Some(profile.clone()),
};
Ok(maker)
}
pub fn get_profile(
&self,
pkg_id: PackageId,
is_member: bool,
is_local: bool,
unit_for: UnitFor,
kind: CompileKind,
) -> Profile {
let maker = self.get_profile_maker(&self.requested_profile).unwrap();
let mut profile = maker.get_profile(Some(pkg_id), is_member, unit_for.is_for_host());
match unit_for.panic_setting() {
PanicSetting::AlwaysUnwind => profile.panic = PanicStrategy::Unwind,
PanicSetting::ReadProfile => {}
}
if profile.debuginfo.is_turned_on() && profile.split_debuginfo.is_none() {
let target = match &kind {
CompileKind::Host => self.rustc_host.as_str(),
CompileKind::Target(target) => target.short_name(),
};
if target.contains("-apple-") {
profile.split_debuginfo = Some("unpacked".into());
}
}
if let Some(v) = self.incremental {
profile.incremental = v;
}
if !is_local {
profile.incremental = false;
}
profile.name = self.requested_profile;
profile
}
pub fn get_profile_run_custom_build(&self, for_unit_profile: &Profile) -> Profile {
let mut result = Profile::default();
result.name = for_unit_profile.name;
result.root = for_unit_profile.root;
result.debuginfo = for_unit_profile.debuginfo;
result.opt_level = for_unit_profile.opt_level;
result.trim_paths = for_unit_profile.trim_paths.clone();
result
}
pub fn base_profile(&self) -> Profile {
let profile_name = self.requested_profile;
let maker = self.get_profile_maker(&profile_name).unwrap();
maker.get_profile(None, true, false)
}
pub fn get_dir_name(&self) -> InternedString {
*self
.dir_names
.get(&self.requested_profile)
.unwrap_or(&self.requested_profile)
}
pub fn validate_packages(
&self,
profiles: Option<&TomlProfiles>,
shell: &mut Shell,
resolve: &Resolve,
) -> CargoResult<()> {
for (name, profile) in &self.by_name {
if self
.original_profiles
.get(name)
.and_then(|orig| orig.package.as_ref())
.is_none()
{
continue;
}
let found = validate_packages_unique(resolve, name, &profile.toml)?;
if let Some(profiles) = profiles {
if let Some(toml_profile) = profiles.get(name) {
validate_packages_unmatched(shell, resolve, name, toml_profile, &found)?;
}
}
}
Ok(())
}
fn get_profile_maker(&self, name: &str) -> CargoResult<&ProfileMaker> {
self.by_name
.get(name)
.ok_or_else(|| anyhow::format_err!("profile `{}` is not defined", name))
}
pub fn profile_names(&self) -> impl Iterator<Item = InternedString> + '_ {
self.by_name.keys().copied()
}
}
#[derive(Debug, Clone)]
struct ProfileMaker {
default: Profile,
toml: Option<TomlProfile>,
}
impl ProfileMaker {
fn new(default: Profile, toml: Option<TomlProfile>) -> ProfileMaker {
ProfileMaker { default, toml }
}
fn get_profile(
&self,
pkg_id: Option<PackageId>,
is_member: bool,
is_for_host: bool,
) -> Profile {
let mut profile = self.default.clone();
if let Some(toml) = &self.toml {
merge_profile(&mut profile, toml);
}
if is_for_host {
profile.opt_level = "0".into();
profile.codegen_units = None;
profile.debuginfo = DebugInfo::Deferred(profile.debuginfo.into_inner());
}
if let Some(toml) = &self.toml {
merge_toml_overrides(pkg_id, is_member, is_for_host, &mut profile, toml);
}
profile
}
}
fn merge_toml_overrides(
pkg_id: Option<PackageId>,
is_member: bool,
is_for_host: bool,
profile: &mut Profile,
toml: &TomlProfile,
) {
if is_for_host {
if let Some(build_override) = &toml.build_override {
merge_profile(profile, build_override);
}
}
if let Some(overrides) = toml.package.as_ref() {
if !is_member {
if let Some(all) = overrides.get(&ProfilePackageSpec::All) {
merge_profile(profile, all);
}
}
if let Some(pkg_id) = pkg_id {
let mut matches = overrides
.iter()
.filter_map(|(key, spec_profile)| match *key {
ProfilePackageSpec::All => None,
ProfilePackageSpec::Spec(ref s) => {
if s.matches(pkg_id) {
Some(spec_profile)
} else {
None
}
}
});
if let Some(spec_profile) = matches.next() {
merge_profile(profile, spec_profile);
assert!(
matches.next().is_none(),
"package `{}` matched multiple package profile overrides",
pkg_id
);
}
}
}
}
fn merge_profile(profile: &mut Profile, toml: &TomlProfile) {
if let Some(ref opt_level) = toml.opt_level {
profile.opt_level = opt_level.0.as_str().into();
}
match toml.lto {
Some(StringOrBool::Bool(b)) => profile.lto = Lto::Bool(b),
Some(StringOrBool::String(ref n)) if is_off(n.as_str()) => profile.lto = Lto::Off,
Some(StringOrBool::String(ref n)) => profile.lto = Lto::Named(n.into()),
None => {}
}
if toml.codegen_backend.is_some() {
profile.codegen_backend = toml.codegen_backend.as_ref().map(InternedString::from);
}
if toml.codegen_units.is_some() {
profile.codegen_units = toml.codegen_units;
}
if let Some(debuginfo) = toml.debug {
profile.debuginfo = DebugInfo::Resolved(debuginfo);
}
if let Some(debug_assertions) = toml.debug_assertions {
profile.debug_assertions = debug_assertions;
}
if let Some(split_debuginfo) = &toml.split_debuginfo {
profile.split_debuginfo = Some(split_debuginfo.into());
}
if let Some(rpath) = toml.rpath {
profile.rpath = rpath;
}
if let Some(panic) = &toml.panic {
profile.panic = match panic.as_str() {
"unwind" => PanicStrategy::Unwind,
"abort" => PanicStrategy::Abort,
"immediate-abort" => PanicStrategy::ImmediateAbort,
_ => panic!("Unexpected panic setting `{}`", panic),
};
}
if let Some(overflow_checks) = toml.overflow_checks {
profile.overflow_checks = overflow_checks;
}
if let Some(incremental) = toml.incremental {
profile.incremental = incremental;
}
if let Some(flags) = &toml.rustflags {
profile.rustflags = flags.iter().map(InternedString::from).collect();
}
if let Some(trim_paths) = &toml.trim_paths {
profile.trim_paths = Some(trim_paths.clone());
}
if let Some(hint_mostly_unused) = toml.hint_mostly_unused {
profile.hint_mostly_unused = Some(hint_mostly_unused);
}
profile.strip = match toml.strip {
Some(StringOrBool::Bool(true)) => Strip::Resolved(StripInner::Named("symbols".into())),
Some(StringOrBool::Bool(false)) => Strip::Resolved(StripInner::None),
Some(StringOrBool::String(ref n)) if n.as_str() == "none" => {
Strip::Resolved(StripInner::None)
}
Some(StringOrBool::String(ref n)) => Strip::Resolved(StripInner::Named(n.into())),
None => Strip::Deferred(StripInner::None),
};
}
#[derive(Clone, Copy, Eq, PartialOrd, Ord, PartialEq, Debug)]
pub enum ProfileRoot {
Release,
Debug,
}
#[derive(Clone, Eq, PartialOrd, Ord, serde::Serialize)]
pub struct Profile {
pub name: InternedString,
pub opt_level: InternedString,
#[serde(skip)] pub root: ProfileRoot,
pub lto: Lto,
pub codegen_backend: Option<InternedString>,
pub codegen_units: Option<u32>,
pub debuginfo: DebugInfo,
pub split_debuginfo: Option<InternedString>,
pub debug_assertions: bool,
pub overflow_checks: bool,
pub rpath: bool,
pub incremental: bool,
pub panic: PanicStrategy,
pub strip: Strip,
#[serde(skip_serializing_if = "Vec::is_empty")] pub rustflags: Vec<InternedString>,
#[serde(skip_serializing_if = "Option::is_none")]
pub trim_paths: Option<TomlTrimPaths>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hint_mostly_unused: Option<bool>,
}
impl Default for Profile {
fn default() -> Profile {
Profile {
name: "".into(),
opt_level: "0".into(),
root: ProfileRoot::Debug,
lto: Lto::Bool(false),
codegen_backend: None,
codegen_units: None,
debuginfo: DebugInfo::Resolved(TomlDebugInfo::None),
debug_assertions: false,
split_debuginfo: None,
overflow_checks: false,
rpath: false,
incremental: false,
panic: PanicStrategy::Unwind,
strip: Strip::Deferred(StripInner::None),
rustflags: vec![],
trim_paths: None,
hint_mostly_unused: None,
}
}
}
compact_debug! {
impl fmt::Debug for Profile {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let (default, default_name) = match self.name.as_str() {
"dev" => (Profile::default_dev(), "default_dev()"),
"release" => (Profile::default_release(false), "default_release()"),
_ => (Profile::default(), "default()"),
};
[debug_the_fields(
name
opt_level
lto
root
codegen_backend
codegen_units
debuginfo
split_debuginfo
debug_assertions
overflow_checks
rpath
incremental
panic
strip
rustflags
trim_paths
hint_mostly_unused
)]
}
}
}
impl fmt::Display for Profile {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Profile({})", self.name)
}
}
impl hash::Hash for Profile {
fn hash<H>(&self, state: &mut H)
where
H: hash::Hasher,
{
self.comparable().hash(state);
}
}
impl cmp::PartialEq for Profile {
fn eq(&self, other: &Self) -> bool {
self.comparable() == other.comparable()
}
}
impl Profile {
fn default_dev() -> Profile {
Profile {
name: "dev".into(),
root: ProfileRoot::Debug,
debuginfo: DebugInfo::Resolved(TomlDebugInfo::Full),
debug_assertions: true,
overflow_checks: true,
incremental: true,
..Profile::default()
}
}
fn default_release(trim_paths_enabled: bool) -> Profile {
let trim_paths = trim_paths_enabled.then(|| TomlTrimPathsValue::Object.into());
Profile {
name: "release".into(),
root: ProfileRoot::Release,
opt_level: "3".into(),
trim_paths,
..Profile::default()
}
}
fn comparable(&self) -> impl Hash + Eq + '_ {
(
self.opt_level,
self.lto,
self.codegen_backend,
self.codegen_units,
self.debuginfo,
self.split_debuginfo,
self.debug_assertions,
self.overflow_checks,
self.rpath,
(self.incremental, self.panic, self.strip),
&self.rustflags,
&self.trim_paths,
)
}
}
#[derive(Debug, Copy, Clone, serde::Serialize)]
#[serde(untagged)]
pub enum DebugInfo {
Resolved(TomlDebugInfo),
Deferred(TomlDebugInfo),
}
impl DebugInfo {
pub fn into_inner(self) -> TomlDebugInfo {
match self {
DebugInfo::Resolved(v) | DebugInfo::Deferred(v) => v,
}
}
pub(crate) fn is_turned_on(&self) -> bool {
!matches!(self.into_inner(), TomlDebugInfo::None)
}
pub(crate) fn is_deferred(&self) -> bool {
matches!(self, DebugInfo::Deferred(_))
}
pub(crate) fn finalize(self) -> Self {
match self {
DebugInfo::Deferred(v) => DebugInfo::Resolved(v),
_ => self,
}
}
pub(crate) fn weaken(self) -> Self {
DebugInfo::Resolved(TomlDebugInfo::None)
}
}
impl PartialEq for DebugInfo {
fn eq(&self, other: &DebugInfo) -> bool {
self.into_inner().eq(&other.into_inner())
}
}
impl Eq for DebugInfo {}
impl Hash for DebugInfo {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.into_inner().hash(state);
}
}
impl PartialOrd for DebugInfo {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.into_inner().partial_cmp(&other.into_inner())
}
}
impl Ord for DebugInfo {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.into_inner().cmp(&other.into_inner())
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
pub enum Lto {
Off,
Bool(bool),
Named(InternedString),
}
impl serde::ser::Serialize for Lto {
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
match self {
Lto::Off => "off".serialize(s),
Lto::Bool(b) => b.to_string().serialize(s),
Lto::Named(n) => n.serialize(s),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, PartialOrd, Ord, serde::Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum PanicStrategy {
Unwind,
Abort,
ImmediateAbort,
}
impl fmt::Display for PanicStrategy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
PanicStrategy::Unwind => "unwind",
PanicStrategy::Abort => "abort",
PanicStrategy::ImmediateAbort => "immediate-abort",
}
.fmt(f)
}
}
#[derive(
Clone, Copy, PartialEq, Eq, Debug, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
)]
pub enum StripInner {
None,
Named(InternedString),
}
impl fmt::Display for StripInner {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
StripInner::None => "none",
StripInner::Named(s) => s.as_str(),
}
.fmt(f)
}
}
#[derive(Clone, Copy, Debug, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Strip {
Resolved(StripInner),
Deferred(StripInner),
}
impl Strip {
pub fn into_inner(self) -> StripInner {
match self {
Strip::Resolved(v) | Strip::Deferred(v) => v,
}
}
pub(crate) fn is_deferred(&self) -> bool {
matches!(self, Strip::Deferred(_))
}
pub(crate) fn strip_debuginfo(self) -> Self {
Strip::Resolved(StripInner::Named("debuginfo".into()))
}
}
impl PartialEq for Strip {
fn eq(&self, other: &Self) -> bool {
self.into_inner().eq(&other.into_inner())
}
}
impl Hash for Strip {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.into_inner().hash(state);
}
}
impl PartialOrd for Strip {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.into_inner().partial_cmp(&other.into_inner())
}
}
impl Ord for Strip {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.into_inner().cmp(&other.into_inner())
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct UnitFor {
host: bool,
host_features: bool,
panic_setting: PanicSetting,
root_compile_kind: CompileKind,
artifact_target_for_features: Option<CompileTarget>,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
enum PanicSetting {
AlwaysUnwind,
ReadProfile,
}
impl UnitFor {
pub fn new_normal(root_compile_kind: CompileKind) -> UnitFor {
UnitFor {
host: false,
host_features: false,
panic_setting: PanicSetting::ReadProfile,
root_compile_kind,
artifact_target_for_features: None,
}
}
pub fn new_host(host_features: bool, root_compile_kind: CompileKind) -> UnitFor {
UnitFor {
host: true,
host_features,
panic_setting: PanicSetting::AlwaysUnwind,
root_compile_kind,
artifact_target_for_features: None,
}
}
pub fn new_compiler(root_compile_kind: CompileKind) -> UnitFor {
UnitFor {
host: false,
host_features: false,
panic_setting: PanicSetting::AlwaysUnwind,
root_compile_kind,
artifact_target_for_features: None,
}
}
pub fn new_test(gctx: &GlobalContext, root_compile_kind: CompileKind) -> UnitFor {
UnitFor {
host: false,
host_features: false,
panic_setting: if gctx.cli_unstable().panic_abort_tests {
PanicSetting::ReadProfile
} else {
PanicSetting::AlwaysUnwind
},
root_compile_kind,
artifact_target_for_features: None,
}
}
pub fn new_host_test(gctx: &GlobalContext, root_compile_kind: CompileKind) -> UnitFor {
let mut unit_for = UnitFor::new_test(gctx, root_compile_kind);
unit_for.host = true;
unit_for.host_features = true;
unit_for
}
pub fn with_dependency(
self,
parent: &Unit,
dep_target: &Target,
root_compile_kind: CompileKind,
) -> UnitFor {
let dep_for_host = dep_target.for_host();
let host_features =
self.host_features || parent.target.is_custom_build() || dep_target.proc_macro();
let panic_setting = if dep_for_host {
PanicSetting::AlwaysUnwind
} else {
self.panic_setting
};
UnitFor {
host: self.host || dep_for_host,
host_features,
panic_setting,
root_compile_kind,
artifact_target_for_features: self.artifact_target_for_features,
}
}
pub fn for_custom_build(self) -> UnitFor {
UnitFor {
host: true,
host_features: self.host_features,
panic_setting: PanicSetting::AlwaysUnwind,
root_compile_kind: self.root_compile_kind,
artifact_target_for_features: self.artifact_target_for_features,
}
}
pub(crate) fn with_artifact_features(mut self, artifact: &Artifact) -> UnitFor {
self.artifact_target_for_features = artifact.target().and_then(|t| t.to_compile_target());
self
}
pub(crate) fn with_artifact_features_from_resolved_compile_kind(
mut self,
kind: Option<CompileKind>,
) -> UnitFor {
self.artifact_target_for_features = kind.and_then(|kind| match kind {
CompileKind::Host => None,
CompileKind::Target(triple) => Some(triple),
});
self
}
pub fn is_for_host(&self) -> bool {
self.host
}
pub fn is_for_host_features(&self) -> bool {
self.host_features
}
fn panic_setting(&self) -> PanicSetting {
self.panic_setting
}
pub(crate) fn map_to_features_for(&self, dep_artifact: Option<&Artifact>) -> FeaturesFor {
FeaturesFor::from_for_host_or_artifact_target(
self.is_for_host_features(),
match dep_artifact {
Some(artifact) => artifact
.target()
.and_then(|t| t.to_resolved_compile_target(self.root_compile_kind)),
None => self.artifact_target_for_features,
},
)
}
pub(crate) fn root_compile_kind(&self) -> CompileKind {
self.root_compile_kind
}
}
fn merge_config_profiles(
ws: &Workspace<'_>,
requested_profile: InternedString,
) -> CargoResult<BTreeMap<InternedString, TomlProfile>> {
let mut profiles = match ws.profiles() {
Some(profiles) => profiles
.get_all()
.iter()
.map(|(k, v)| (InternedString::new(k), v.clone()))
.collect(),
None => BTreeMap::new(),
};
let mut check_to_add = HashSet::new();
check_to_add.insert(requested_profile);
for (name, profile) in &mut profiles {
if let Some(config_profile) = get_config_profile(ws, name)? {
profile.merge(&config_profile);
}
if let Some(inherits) = &profile.inherits {
check_to_add.insert(inherits.into());
}
}
for name in ["dev", "release", "test", "bench"] {
check_to_add.insert(name.into());
}
let mut current = HashSet::new();
while !check_to_add.is_empty() {
std::mem::swap(&mut current, &mut check_to_add);
for name in current.drain() {
if !profiles.contains_key(name.as_str()) {
if let Some(config_profile) = get_config_profile(ws, &name)? {
if let Some(inherits) = &config_profile.inherits {
check_to_add.insert(inherits.into());
}
profiles.insert(name, config_profile);
}
}
}
}
Ok(profiles)
}
fn get_config_profile(ws: &Workspace<'_>, name: &str) -> CargoResult<Option<TomlProfile>> {
let profile: Option<context::Value<TomlProfile>> =
ws.gctx().get(&format!("profile.{}", name))?;
let Some(profile) = profile else {
return Ok(None);
};
let mut warnings = Vec::new();
validate_profile(
&profile.val,
name,
ws.gctx().cli_unstable(),
ws.unstable_features(),
&mut warnings,
)
.with_context(|| {
format!(
"config profile `{}` is not valid (defined in `{}`)",
name, profile.definition
)
})?;
for warning in warnings {
ws.gctx().shell().warn(warning)?;
}
Ok(Some(profile.val))
}
fn validate_packages_unique(
resolve: &Resolve,
name: &str,
toml: &Option<TomlProfile>,
) -> CargoResult<HashSet<PackageIdSpec>> {
let Some(toml) = toml else {
return Ok(HashSet::new());
};
let Some(overrides) = toml.package.as_ref() else {
return Ok(HashSet::new());
};
let mut found = HashSet::new();
for pkg_id in resolve.iter() {
let matches: Vec<&PackageIdSpec> = overrides
.keys()
.filter_map(|key| match *key {
ProfilePackageSpec::All => None,
ProfilePackageSpec::Spec(ref spec) => {
if spec.matches(pkg_id) {
Some(spec)
} else {
None
}
}
})
.collect();
match matches.len() {
0 => {}
1 => {
found.insert(matches[0].clone());
}
_ => {
let specs = matches
.iter()
.map(|spec| spec.to_string())
.collect::<Vec<_>>()
.join(", ");
bail!(
"multiple package overrides in profile `{}` match package `{}`\n\
found package specs: {}",
name,
pkg_id,
specs
);
}
}
}
Ok(found)
}
fn validate_packages_unmatched(
shell: &mut Shell,
resolve: &Resolve,
name: &str,
toml: &TomlProfile,
found: &HashSet<PackageIdSpec>,
) -> CargoResult<()> {
let Some(overrides) = toml.package.as_ref() else {
return Ok(());
};
let missing_specs = overrides.keys().filter_map(|key| {
if let ProfilePackageSpec::Spec(ref spec) = *key {
if !found.contains(spec) {
return Some(spec);
}
}
None
});
for spec in missing_specs {
let name_matches: Vec<String> = resolve
.iter()
.filter_map(|pkg_id| {
if pkg_id.name() == spec.name() {
Some(pkg_id.to_string())
} else {
None
}
})
.collect();
if name_matches.is_empty() {
let suggestion = closest_msg(
&spec.name(),
resolve.iter(),
|p| p.name().as_str(),
"package",
);
shell.warn(format!(
"profile package spec `{}` in profile `{}` did not match any packages{}",
spec, name, suggestion
))?;
} else {
shell.warn(format!(
"profile package spec `{}` in profile `{}` \
has a version or URL that does not match any of the packages: {}",
spec,
name,
name_matches.join(", ")
))?;
}
}
Ok(())
}
fn is_off(s: &str) -> bool {
matches!(s, "off" | "n" | "no" | "none")
}