Skip to content

Commit 5415a34

Browse files
committed
Add package spec support to profile overrides.
Note: It errors out if multiple overrides match the same package. This could potentially merge the overrides together with a hierarchy similar to CSS specificity. However, that seems overkill for now.
1 parent d0d3cb5 commit 5415a34

File tree

7 files changed

+304
-41
lines changed

7 files changed

+304
-41
lines changed

src/cargo/core/compiler/context/unit_dependencies.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ fn new_unit<'a>(
329329
mode: CompileMode,
330330
) -> Unit<'a> {
331331
let profile = bcx.profiles.get_profile(
332-
&pkg.name(),
332+
&pkg.package_id(),
333333
bcx.ws.is_member(pkg),
334334
profile_for,
335335
mode,

src/cargo/core/package_id_spec.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::collections::HashMap;
22
use std::fmt;
33

44
use semver::Version;
5+
use serde::{de, ser};
56
use url::Url;
67

78
use core::PackageId;
@@ -17,7 +18,7 @@ use util::errors::{CargoResult, CargoResultExt};
1718
/// If any of the optional fields are omitted, then the package id may be ambiguous, there may be
1819
/// more than one package/version/url combo that will match. However, often just the name is
1920
/// sufficient to uniquely define a package id.
20-
#[derive(Clone, PartialEq, Eq, Debug)]
21+
#[derive(Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)]
2122
pub struct PackageIdSpec {
2223
name: String,
2324
version: Option<Version>,
@@ -253,6 +254,25 @@ impl fmt::Display for PackageIdSpec {
253254
}
254255
}
255256

257+
impl ser::Serialize for PackageIdSpec {
258+
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
259+
where
260+
S: ser::Serializer,
261+
{
262+
self.to_string().serialize(s)
263+
}
264+
}
265+
266+
impl<'de> de::Deserialize<'de> for PackageIdSpec {
267+
fn deserialize<D>(d: D) -> Result<PackageIdSpec, D::Error>
268+
where
269+
D: de::Deserializer<'de>,
270+
{
271+
let string = String::deserialize(d)?;
272+
PackageIdSpec::parse(&string).map_err(de::Error::custom)
273+
}
274+
}
275+
256276
#[cfg(test)]
257277
mod tests {
258278
use core::{PackageId, SourceId};

src/cargo/core/profiles.rs

Lines changed: 112 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ use std::{cmp, fmt, hash};
33

44
use core::compiler::CompileMode;
55
use core::interning::InternedString;
6-
use core::Shell;
6+
use core::{PackageId, PackageIdSpec, PackageSet, Shell};
77
use util::lev_distance::lev_distance;
8-
use util::toml::{StringOrBool, TomlProfile, U32OrBool};
8+
use util::toml::{ProfilePackageSpec, StringOrBool, TomlProfile, U32OrBool};
99
use util::CargoResult;
1010

1111
/// Collection of all user profiles.
@@ -55,7 +55,7 @@ impl Profiles {
5555
/// workspace.
5656
pub fn get_profile(
5757
&self,
58-
pkg_name: &str,
58+
pkg_id: &PackageId,
5959
is_member: bool,
6060
profile_for: ProfileFor,
6161
mode: CompileMode,
@@ -86,7 +86,7 @@ impl Profiles {
8686
CompileMode::Bench => &self.bench,
8787
CompileMode::Doc { .. } => &self.doc,
8888
};
89-
let mut profile = maker.profile_for(pkg_name, is_member, profile_for);
89+
let mut profile = maker.profile_for(Some(pkg_id), is_member, profile_for);
9090
// `panic` should not be set for tests/benches, or any of their
9191
// dependencies.
9292
if profile_for == ProfileFor::TestDependency || mode.is_any_test() {
@@ -112,18 +112,14 @@ impl Profiles {
112112
/// select for the package that was actually built.
113113
pub fn base_profile(&self, release: bool) -> Profile {
114114
if release {
115-
self.release.profile_for("", true, ProfileFor::Any)
115+
self.release.profile_for(None, true, ProfileFor::Any)
116116
} else {
117-
self.dev.profile_for("", true, ProfileFor::Any)
117+
self.dev.profile_for(None, true, ProfileFor::Any)
118118
}
119119
}
120120

121121
/// Used to check for overrides for non-existing packages.
122-
pub fn validate_packages(
123-
&self,
124-
shell: &mut Shell,
125-
packages: &HashSet<&str>,
126-
) -> CargoResult<()> {
122+
pub fn validate_packages(&self, shell: &mut Shell, packages: &PackageSet) -> CargoResult<()> {
127123
self.dev.validate_packages(shell, packages)?;
128124
self.release.validate_packages(shell, packages)?;
129125
self.test.validate_packages(shell, packages)?;
@@ -149,7 +145,12 @@ struct ProfileMaker {
149145
}
150146

151147
impl ProfileMaker {
152-
fn profile_for(&self, pkg_name: &str, is_member: bool, profile_for: ProfileFor) -> Profile {
148+
fn profile_for(
149+
&self,
150+
pkg_id: Option<&PackageId>,
151+
is_member: bool,
152+
profile_for: ProfileFor,
153+
) -> Profile {
153154
let mut profile = self.default;
154155
if let Some(ref toml) = self.toml {
155156
merge_profile(&mut profile, toml);
@@ -160,19 +161,38 @@ impl ProfileMaker {
160161
}
161162
if let Some(ref overrides) = toml.overrides {
162163
if !is_member {
163-
if let Some(star) = overrides.get("*") {
164-
merge_profile(&mut profile, star);
164+
if let Some(all) = overrides.get(&ProfilePackageSpec::All) {
165+
merge_profile(&mut profile, all);
165166
}
166167
}
167-
if let Some(byname) = overrides.get(pkg_name) {
168-
merge_profile(&mut profile, byname);
168+
if let Some(pkg_id) = pkg_id {
169+
let mut matches = overrides.iter().filter_map(
170+
|(key, spec_profile)| match key {
171+
&ProfilePackageSpec::All => None,
172+
&ProfilePackageSpec::Spec(ref s) => if s.matches(pkg_id) {
173+
Some(spec_profile)
174+
} else {
175+
None
176+
},
177+
},
178+
);
179+
if let Some(spec_profile) = matches.next() {
180+
merge_profile(&mut profile, spec_profile);
181+
// `validate_packages` should ensure that there are
182+
// no additional matches.
183+
assert!(
184+
matches.next().is_none(),
185+
"package `{}` matched multiple profile overrides",
186+
pkg_id
187+
);
188+
}
169189
}
170190
}
171191
}
172192
profile
173193
}
174194

175-
fn validate_packages(&self, shell: &mut Shell, packages: &HashSet<&str>) -> CargoResult<()> {
195+
fn validate_packages(&self, shell: &mut Shell, packages: &PackageSet) -> CargoResult<()> {
176196
let toml = match self.toml {
177197
Some(ref toml) => toml,
178198
None => return Ok(()),
@@ -181,23 +201,88 @@ impl ProfileMaker {
181201
Some(ref overrides) => overrides,
182202
None => return Ok(()),
183203
};
184-
for key in overrides.keys().filter(|k| k.as_str() != "*") {
185-
if !packages.contains(key.as_str()) {
204+
// Verify that a package doesn't match multiple spec overrides.
205+
let mut found = HashSet::new();
206+
for pkg_id in packages.package_ids() {
207+
let matches: Vec<&PackageIdSpec> = overrides
208+
.keys()
209+
.filter_map(|key| match key {
210+
&ProfilePackageSpec::All => None,
211+
&ProfilePackageSpec::Spec(ref spec) => if spec.matches(pkg_id) {
212+
Some(spec)
213+
} else {
214+
None
215+
},
216+
})
217+
.collect();
218+
match matches.len() {
219+
0 => {}
220+
1 => {
221+
found.insert(matches[0].clone());
222+
}
223+
_ => {
224+
let specs = matches
225+
.iter()
226+
.map(|spec| spec.to_string())
227+
.collect::<Vec<_>>()
228+
.join(", ");
229+
bail!(
230+
"multiple profile overrides in profile `{}` match package `{}`\n\
231+
found profile override specs: {}",
232+
self.default.name,
233+
pkg_id,
234+
specs
235+
);
236+
}
237+
}
238+
}
239+
240+
// Verify every override matches at least one package.
241+
let missing_specs = overrides.keys().filter_map(|key| {
242+
if let &ProfilePackageSpec::Spec(ref spec) = key {
243+
if !found.contains(spec) {
244+
return Some(spec);
245+
}
246+
}
247+
None
248+
});
249+
for spec in missing_specs {
250+
// See if there is an exact name match.
251+
let name_matches: Vec<String> = packages
252+
.package_ids()
253+
.filter_map(|pkg_id| {
254+
if pkg_id.name().as_str() == spec.name() {
255+
Some(pkg_id.to_string())
256+
} else {
257+
None
258+
}
259+
})
260+
.collect();
261+
if name_matches.len() == 0 {
186262
let suggestion = packages
187-
.iter()
188-
.map(|p| (lev_distance(key, p), p))
263+
.package_ids()
264+
.map(|p| (lev_distance(spec.name(), &p.name()), p.name()))
189265
.filter(|&(d, _)| d < 4)
190266
.min_by_key(|p| p.0)
191267
.map(|p| p.1);
192268
match suggestion {
193269
Some(p) => shell.warn(format!(
194-
"package `{}` for profile override not found\n\nDid you mean `{}`?",
195-
key, p
270+
"profile override spec `{}` did not match any packages\n\n\
271+
Did you mean `{}`?",
272+
spec, p
196273
))?,
197-
None => {
198-
shell.warn(format!("package `{}` for profile override not found", key))?
199-
}
200-
};
274+
None => shell.warn(format!(
275+
"profile override spec `{}` did not match any packages",
276+
spec
277+
))?,
278+
}
279+
} else {
280+
shell.warn(format!(
281+
"version or URL in profile override spec `{}` does not \
282+
match any of the packages: {}",
283+
spec,
284+
name_matches.join(", ")
285+
))?;
201286
}
202287
}
203288
Ok(())

src/cargo/ops/cargo_clean.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,15 @@ pub fn clean(ws: &Workspace, opts: &CleanOptions) -> CargoResult<()> {
6060
for profile_for in ProfileFor::all_values() {
6161
let profile = if mode.is_run_custom_build() {
6262
profiles.get_profile_run_custom_build(&profiles.get_profile(
63-
&pkg.name(),
63+
pkg.package_id(),
6464
ws.is_member(pkg),
6565
*profile_for,
6666
CompileMode::Build,
6767
opts.release,
6868
))
6969
} else {
7070
profiles.get_profile(
71-
&pkg.name(),
71+
pkg.package_id(),
7272
ws.is_member(pkg),
7373
*profile_for,
7474
*mode,

src/cargo/ops/cargo_compile.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -254,11 +254,7 @@ pub fn compile_ws<'a>(
254254
}
255255

256256
let profiles = ws.profiles();
257-
let package_names = packages
258-
.package_ids()
259-
.map(|pid| pid.name().as_str())
260-
.collect::<HashSet<_>>();
261-
profiles.validate_packages(&mut config.shell(), &package_names)?;
257+
profiles.validate_packages(&mut config.shell(), &packages)?;
262258

263259
let mut extra_compiler_args = None;
264260

@@ -494,7 +490,7 @@ fn generate_targets<'a>(
494490
default_arch_kind
495491
};
496492
let profile = profiles.get_profile(
497-
&pkg.name(),
493+
pkg.package_id(),
498494
ws.is_member(pkg),
499495
profile_for,
500496
target_mode,

src/cargo/util/toml/mod.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,10 +380,44 @@ pub struct TomlProfile {
380380
pub panic: Option<String>,
381381
pub overflow_checks: Option<bool>,
382382
pub incremental: Option<bool>,
383-
pub overrides: Option<BTreeMap<String, TomlProfile>>,
383+
pub overrides: Option<BTreeMap<ProfilePackageSpec, TomlProfile>>,
384384
pub build_override: Option<Box<TomlProfile>>,
385385
}
386386

387+
#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
388+
pub enum ProfilePackageSpec {
389+
Spec(PackageIdSpec),
390+
All,
391+
}
392+
393+
impl ser::Serialize for ProfilePackageSpec {
394+
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
395+
where
396+
S: ser::Serializer,
397+
{
398+
match *self {
399+
ProfilePackageSpec::Spec(ref spec) => spec.serialize(s),
400+
ProfilePackageSpec::All => "*".serialize(s),
401+
}
402+
}
403+
}
404+
405+
impl<'de> de::Deserialize<'de> for ProfilePackageSpec {
406+
fn deserialize<D>(d: D) -> Result<ProfilePackageSpec, D::Error>
407+
where
408+
D: de::Deserializer<'de>,
409+
{
410+
let string = String::deserialize(d)?;
411+
if string == "*" {
412+
Ok(ProfilePackageSpec::All)
413+
} else {
414+
PackageIdSpec::parse(&string)
415+
.map_err(de::Error::custom)
416+
.map(|s| ProfilePackageSpec::Spec(s))
417+
}
418+
}
419+
}
420+
387421
impl TomlProfile {
388422
fn validate(
389423
&self,

0 commit comments

Comments
 (0)