Skip to content
Commits on Source (9)
[package]
version = "0.18.0"
version = "0.19.0"
edition = "2021"
name = "cargo_toml"
authors = ["Kornel <kornel@geekhood.net>"]
......
......@@ -23,7 +23,7 @@ pub trait AbstractFilesystem {
Err(io::Error::new(io::ErrorKind::Unsupported, "AbstractFilesystem::read_root_workspace unimplemented"))
}
/// The `rel_path_hint` may be specified explicitly by `package.workspace` (it may be relative like `"../", without `Cargo.toml`) or `None`,
/// The `rel_path_hint` may be specified explicitly by `package.workspace` (it may be relative like `"../"`, without `Cargo.toml`) or `None`,
/// which means you have to search for workspace's `Cargo.toml` in parent directories.
///
/// Read and parse the root workspace manifest TOML file and return the path it's been read from.
......
......@@ -22,14 +22,23 @@ use std::path::{Path, PathBuf};
use std::{fs, io};
pub use toml::Value;
/// Dependencies. The keys in this map may not be crate names if `package` is used, but may be feature names if they're optional.
/// Dependencies. The keys in this map are not always crate names, this can be overriden by the `package` field, and there may be multiple copies of the same crate.
/// Optional dependencies may create implicit features, see the [`features`] module for dealing with this.
pub type DepsSet = BTreeMap<String, Dependency>;
/// Config target (see [`parse_cfg`](https://lib.rs/parse_cfg) crate) + deps for the target.
pub type TargetDepsSet = BTreeMap<String, Target>;
/// `[features]` section. `default` is special.
/// The `[features]` section. This set may be incomplete!
///
/// The `default` is special, and there may be more features
/// implied by optional dependencies.
/// See the [`features`] module for more info.
pub type FeatureSet = BTreeMap<String, Vec<String>>;
/// Locally replace dependencies
pub type PatchSet = BTreeMap<String, DepsSet>;
/// A set of lints.
pub type LintSet = BTreeMap<String, Lint>;
/// Lint groups such as [lints.rust].
pub type LintGroups = BTreeMap<String, LintSet>;
mod afs;
mod error;
......@@ -71,7 +80,14 @@ pub struct Manifest<Metadata = Value> {
#[serde(default, skip_serializing_if = "TargetDepsSet::is_empty")]
pub target: TargetDepsSet,
/// `[features]` section
/// The `[features]` section. This set may be incomplete!
///
/// Optional dependencies may create implied Cargo features.
/// This features section also supports microsyntax with `dep:`, `/`, and `?`
/// for managing dependencies and their features.io
///
/// This crate has an optional [`features`] module for dealing with this
/// complexity and getting the real list of features.
#[serde(default, skip_serializing_if = "FeatureSet::is_empty")]
pub features: FeatureSet,
......@@ -112,6 +128,10 @@ pub struct Manifest<Metadata = Value> {
/// Examples
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub example: Vec<Product>,
/// Lints
#[serde(default, skip_serializing_if = "Option::is_none")]
pub lints: Option<Lints>,
}
/// A manifest can contain both a package and workspace-wide properties
......@@ -147,6 +167,10 @@ pub struct Workspace<Metadata = Value> {
/// Template for needs_workspace_inheritance
#[serde(default, skip_serializing_if = "DepsSet::is_empty")]
pub dependencies: DepsSet,
/// Workspace-level lint groups
#[serde(default, skip_serializing_if = "Option::is_none")]
pub lints: Option<LintGroups>,
}
/// Workspace can predefine properties that can be inherited via `{ workspace = true }` in its member packages.
......@@ -284,14 +308,6 @@ impl<Metadata: for<'a> Deserialize<'a>> Manifest<Metadata> {
if package.version.get().map_or(false, |v| v == "0.0.0") && package.publish.get().map_or(false, |p| p.is_default()) {
package.publish = Inheritable::Set(Publish::Flag(false));
}
} else if manifest.package.is_none() && manifest.workspace.is_none() {
// Some old crates lack the `[package]` header
let val: Value = toml::from_str(cargo_toml_content)?;
if let Some(project) = val.get("project") {
manifest.package = Some(project.clone().try_into()?);
} else {
manifest.package = Some(val.try_into()?);
}
}
Ok(manifest)
}
......@@ -670,6 +686,7 @@ impl<Metadata: Default> Default for Manifest<Metadata> {
bench: Default::default(),
test: Default::default(),
example: Default::default(),
lints: Default::default(),
}
}
}
......@@ -958,7 +975,7 @@ pub struct Product {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub crate_type: Vec<String>,
/// The required-features field specifies which features the product needs in order to be built.
/// The `required-features` field specifies which features the product needs in order to be built.
/// If any of the required features are not selected, the product will be skipped.
/// This is only relevant for the `[[bin]]`, `[[bench]]`, `[[test]]`, and `[[example]]` sections,
/// it has no effect on `[lib]`.
......@@ -1056,7 +1073,7 @@ impl Dependency {
}
}
/// Enable extra features for this dep.
/// Enable extra features for this dep, in addition to the `default` features controlled via `default_features`.
#[inline]
#[must_use]
pub fn req_features(&self) -> &[String] {
......@@ -1067,7 +1084,8 @@ impl Dependency {
}
}
/// Is it optional. Note that optional deps can be used as features, unless features use `dep:`/`?` syntax for them..
/// Is it optional. Note that optional deps can be used as features, unless features use `dep:`/`?` syntax for them.
/// See the [`features`] module for more info.
#[inline]
#[must_use]
pub fn optional(&self) -> bool {
......@@ -1174,10 +1192,11 @@ pub struct DependencyDetail {
/// NB: Not allowed at workspace level
///
/// If not used with `dep:` or `?/` syntax in `[features]`, this also creates an implicit feature.
/// See the [`features`] module for more info.
#[serde(default, skip_serializing_if = "is_false")]
pub optional: bool,
/// Enable `default` features of the dependency.
/// Enable the `default` set of features of the dependency (enabled by default).
#[serde(default = "default_true", skip_serializing_if = "is_true")]
pub default_features: bool,
......@@ -1820,3 +1839,39 @@ impl Display for Resolver {
impl Default for Resolver {
fn default() -> Self { Self::V1 }
}
/// Lint definition.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Lint {
Simple(LintLevel),
Detailed {
level: LintLevel,
/// Controls which lints or lint groups override other lint groups.
priority: Option<i32>,
}
}
/// Lint level.
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum LintLevel {
Allow,
Warn,
Deny,
Forbid,
}
/// `[lints]` section.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Lints {
/// Inherit lint rules from the workspace.
pub workspace: bool,
/// Lint groups
#[serde(flatten)]
pub groups: LintGroups,
}
//! Helper for parsing syntax of the `[features]` section
//! Helper for parsing the microsyntax of the `[features]` section and computing implied features from optional dependencies.
use crate::{Dependency, Manifest, Product, DepsSet, TargetDepsSet};
use std::borrow::Cow;
use std::collections::hash_map::{Entry, RandomState};
use std::collections::{HashMap, BTreeMap, BTreeSet};
use std::hash::BuildHasher;
......@@ -24,50 +25,59 @@ pub struct Resolver<'config, Hasher = RandomState> {
///
/// The extra `Hasher` arg is for optionally using [`ahash`](https://lib.rs/ahash).
#[derive(Debug)]
#[non_exhaustive]
pub struct Features<'manifest, 'deps, Hasher = RandomState> {
/// All features, resolved and normalized
///
/// Default features are literally under the "default" key.
pub features: HashMap<&'manifest str, Feature<'manifest>, Hasher>,
/// Dependencies referenced by the features, keyed by dependencies' name/identifier in TOML (that's not always the crate name)
/// FYI, dependencies referenced by the features, keyed by dependencies' name/identifier in TOML (that's not always the crate name)
///
/// This doesn't include *all* dependencies. Dependencies unaffected by feature selection are skipped.
/// This doesn't include *all* dependencies. Dependencies unaffected by any features selection are skipped.
pub dependencies: HashMap<&'deps str, FeatureDependency<'deps>, Hasher>,
/// True if there were features with names staring with `_` and were inlined and merged into other features
///
/// See arg of [`new_with_hasher_and_filter`](Resolver::new_with_hasher_and_filter) to disable removal.
pub removed_hidden_features: bool,
/// A redirect from removed feature to its replacements
pub hidden_features: HashMap<&'manifest str, BTreeSet<&'manifest str>, Hasher>,
}
/// How an enabled feature affects the dependency
#[derive(Debug, PartialEq, Clone)]
#[non_exhaustive]
pub struct DepAction<'a> {
/// Uses `?` syntax, so it doesn't enable the depenency
pub is_conditional: bool,
/// Uses `dep:` or `?` syntax, so it doesn't imply a feature name
pub is_dep_only: bool,
/// Features of the dependency to enable (the text after slash, possibly aggregated from multiple items)
pub dep_features: BTreeSet<&'a str>,
pub dep_features: BTreeSet<Cow<'a, str>>,
}
/// A feature from `[features]` with all the details
#[derive(Debug)]
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Feature<'a> {
/// Name of the feature
pub key: &'a str,
/// Deps by their manifest key (the key isn't always the same as the crate name)
/// Deps this enables or modifies, by their manifest key (the key isn't always the same as the crate name)
///
/// This set is shallow, see [`Feature::enables_recursive`].
/// This set is shallow (this feature may also be enabling other features that enable more deps), see [`Feature::enables_recursive`].
pub enables_deps: BTreeMap<&'a str, DepAction<'a>>,
/// Enables these explicitly named features
///
/// This set is shallow, see [`Feature::enables_recursive`].
/// This set is shallow, and the features listed here may be enabling more features, see [`Feature::enables_recursive`].
/// Note that Cargo permits infinite loops (A enables B, B enables A).
pub enables_features: BTreeSet<&'a str>,
/// Keys of other features that enable this feature (this is shallow, not recursive)
/// Keys of other features that directly enable this feature (this is shallow, not recursive)
pub enabled_by: BTreeSet<&'a str>,
/// Names of binaries that have `required-features = [this feature]` (this is shallow, not recursive)
/// Names of binaries that mention this feature in their `required-features = [this feature]`
///
/// Name of the default unnamed binary is set to the package name, and not normalized.
pub required_by_bins: Vec<&'a str>,
/// If true, it's from `[features]`. If false, it's from `[dependencies]`.
......@@ -97,7 +107,7 @@ impl<'a> Feature<'a> {
self.enabled_by.iter().copied().filter(|&e| e != "default")
}
/// Is any feature using this one?
/// Is any other feature using this one?
#[inline]
#[must_use]
pub fn is_referenced(&self) -> bool {
......@@ -106,7 +116,7 @@ impl<'a> Feature<'a> {
/// Finds all features and dependencies that this feature enables, recursively and exhaustively
///
/// The first HashMap is features by their key, the second is dependencies by their key
/// The first `HashMap` is features by their key, the second is dependencies by their key. It includes only dependencies changed by the features, not all crate dependencies.
#[must_use]
pub fn enables_recursive<S: BuildHasher + Default>(&'a self, features: &'a HashMap<&'a str, Feature<'a>, S>) -> (HashMap<&'a str, &'a Feature<'a>, S>, DependenciesEnabledByFeatures<'a, S>) {
let mut features_set = HashMap::with_capacity_and_hasher(self.enabled_by.len() + self.enabled_by.len()/2, S::default());
......@@ -120,7 +130,7 @@ impl<'a> Feature<'a> {
if features_set.insert(self.key, self).is_none() {
for (&dep_key, action) in &self.enables_deps {
if !action.is_conditional {
deps_set.entry(dep_key).or_insert_with(Vec::new).push((self.key, action));
deps_set.entry(dep_key).or_default().push((self.key, action));
}
}
for &key in &self.enables_features {
......@@ -132,56 +142,37 @@ impl<'a> Feature<'a> {
}
}
/// Extra info for dependency referenced by a feature
#[derive(Debug)]
pub struct FeatureDependencyDetail<'dep> {
/// Features may refer to non-optional dependencies, only enable *their* features
pub is_optional: bool,
/// If it's enabled by default, other targets are ignored and this is empty
pub only_for_targets: BTreeSet<&'dep str>,
/// Details about this dependency
pub dep: &'dep Dependency,
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TargetKey<'a> {
pub kind: Kind,
/// cfg. None for all targets.
pub target: Option<&'a str>,
}
/// A dependency referenced by a feature
#[derive(Debug)]
#[non_exhaustive]
pub struct FeatureDependency<'dep> {
/// Actual crate of this dependency. Note that multiple dependencies can be the same crate, in different versions.
pub crate_name: &'dep str,
/// At least one of these will be set
pub normal: Option<FeatureDependencyDetail<'dep>>,
pub build: Option<FeatureDependencyDetail<'dep>>,
pub dev: Option<FeatureDependencyDetail<'dep>>,
/// By kind and target
pub targets: BTreeMap<TargetKey<'dep>, &'dep Dependency>,
}
impl<'dep> FeatureDependency<'dep> {
#[inline]
#[must_use]
pub fn dep(&self) -> &'dep Dependency {
self.detail().0.dep
}
#[inline]
#[must_use]
pub fn detail(&self) -> (&FeatureDependencyDetail<'dep>, Kind) {
[
(self.normal.as_ref(), Kind::Normal),
(self.build.as_ref(), Kind::Build),
(self.dev.as_ref(), Kind::Dev)
].into_iter()
.find_map(|(detail, kind)| Some((detail?, kind)))
.unwrap()
self.detail().0
}
/// Extra metadata for the most common usage (normal > build > dev) of this dependency
#[inline]
#[must_use]
fn get_mut_entry(&mut self, kind: Kind) -> &mut Option<FeatureDependencyDetail<'dep>> {
match kind {
Kind::Normal => &mut self.normal,
Kind::Build => &mut self.build,
Kind::Dev => &mut self.dev,
}
pub fn detail(&self) -> (&'dep Dependency, Kind) {
let (k, dep) = self.targets.iter().next().unwrap();
(dep, k.kind)
}
}
......@@ -228,12 +219,13 @@ impl<'manifest, 'config, RandomState: BuildHasher + Default> Resolver<'config, R
Self::remove_redundant_dep_action_features(&mut features, &dependencies);
Self::set_enabled_by(&mut features);
let removed_hidden_features = self.remove_hidden_features(&mut features);
let hidden_features = self.remove_hidden_features(&mut features);
Features {
features,
dependencies,
removed_hidden_features,
removed_hidden_features: !hidden_features.is_empty(),
hidden_features,
}
}
......@@ -256,12 +248,13 @@ impl<'manifest, 'config, RandomState: BuildHasher + Default> Resolver<'config, R
Self::remove_redundant_dep_action_features(&mut features, &dependencies);
Self::set_enabled_by(&mut features);
let removed_hidden_features = self.remove_hidden_features(&mut features);
let hidden_features = self.remove_hidden_features(&mut features);
Features {
features,
dependencies,
removed_hidden_features,
removed_hidden_features: !hidden_features.is_empty(),
hidden_features,
}
}
}
......@@ -270,6 +263,7 @@ impl<'manifest, 'config, RandomState: BuildHasher + Default> Resolver<'config, R
///
/// Note about lifetimes: it's not possible to make `&Dependency` on the fly.
/// You will have to collect *owned* `Dependency` objects to a `Vec` or `HashMap` first.
#[derive(Debug, Clone)]
pub struct ParseDependency<'a, 'tmp> {
/// Name/id of the dependency, not always the crate name
pub key: &'a str,
......@@ -280,11 +274,12 @@ pub struct ParseDependency<'a, 'tmp> {
pub dep: &'tmp Dependency,
}
#[derive(Copy, Clone, PartialEq)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
pub enum Kind {
#[default]
Normal,
Dev,
Build,
Dev,
}
......@@ -320,7 +315,7 @@ impl<'a, 'c, S: BuildHasher + Default> Resolver<'c, S> {
});
if !is_conditional { action.is_conditional = false; }
if is_dep_only { action.is_dep_only = true; }
if let Some(df) = dep_feature { action.dep_features.insert(df); }
if let Some(df) = dep_feature { action.dep_features.insert(Cow::Borrowed(df)); }
} else {
// dep/foo can be both, and missing enables_deps is added later after checking all for dep:
enables_features.insert(atarget);
......@@ -351,37 +346,13 @@ impl<'a, 'c, S: BuildHasher + Default> Resolver<'c, S> {
if !is_optional && named_using_dep_syntax.is_none() && matches!(entry, Entry::Vacant(_)) {
return;
}
let entry = entry.or_insert_with(move || FeatureDependency {
crate_name: dep.package().unwrap_or(key),
normal: None,
build: None,
dev: None,
targets: BTreeMap::new(),
});
match entry.get_mut_entry(dep_kind) {
Some(out) => {
// target-specific non-optional doesn't affect optionality for other targets
if let Some(target) = only_for_target {
// if the dep is already used for all targets, then the target-specific details won't change that
if !out.only_for_targets.is_empty() {
out.only_for_targets.insert(target);
}
} else {
out.only_for_targets.clear();
if !is_optional && out.is_optional {
out.is_optional = false;
out.dep = dep;
}
}
},
out @ None => {
*out = Some(FeatureDependencyDetail {
dep,
is_optional,
// if creating, this is the first time seeing the dep, so it is target-specific, since general deps were processed earlier
only_for_targets: only_for_target.into_iter().collect(),
});
}
};
entry.targets.entry(TargetKey { kind: dep_kind, target: only_for_target }).or_insert(dep);
// explicit features never overlap with deps, unless using "dep:" syntax.
......@@ -459,7 +430,8 @@ impl<'a, 'c, S: BuildHasher + Default> Resolver<'c, S> {
.filter(|(_, action)| !action.dep_features.is_empty())
.for_each(|(dep_key, action)| {
if let Some(dep) = dependencies.get(dep_key).and_then(|d| d.dep().detail()) {
action.dep_features.retain(move |&dep_f| {
action.dep_features.retain(move |dep_f| {
let dep_f = &**dep_f;
(!dep.default_features || dep_f != "default") &&
!dep.features.iter().any(|k| k == dep_f)
});
......@@ -488,17 +460,19 @@ impl<'a, 'c, S: BuildHasher + Default> Resolver<'c, S> {
/// find `__features` and inline them
#[inline(never)]
fn remove_hidden_features(&self, features: &mut HashMap<&'a str, Feature<'a>, S>) -> bool {
fn remove_hidden_features(&self, features: &mut HashMap<&'a str, Feature<'a>, S>) -> HashMap<&'a str, BTreeSet<&'a str>, S> {
let features_to_remove: BTreeSet<_> = features.keys().copied().filter(|&k| {
k.starts_with('_') && !self.always_keep.map_or(false, |cb| (cb)(k)) // if user thinks that is useful info
}).collect();
let mut removed_mapping = HashMap::<_, _, S>::default();
if features_to_remove.is_empty() {
return false;
return removed_mapping;
}
features_to_remove.into_iter().for_each(|key| {
let Some(janky) = features.remove(key) else { return };
let Some(mut janky) = features.remove(key) else { return };
janky.enabled_by.iter().for_each(|&parent_key| if let Some(parent) = features.get_mut(parent_key) {
parent.enabled_by.remove(janky.key); // just in case it's circular
......@@ -512,7 +486,7 @@ impl<'a, 'c, S: BuildHasher + Default> Resolver<'c, S> {
.and_modify(|old| {
if !ja.is_conditional { old.is_conditional = false; }
if ja.is_dep_only { old.is_dep_only = true; }
old.dep_features.extend(&ja.dep_features);
old.dep_features.extend(ja.dep_features.iter().cloned());
})
.or_insert_with(|| ja.clone());
});
......@@ -539,8 +513,10 @@ impl<'a, 'c, S: BuildHasher + Default> Resolver<'c, S> {
d.enabled_by.remove(janky.key);
}
});
removed_mapping.entry(janky.key).or_default().append(&mut janky.enables_features);
});
true
removed_mapping
}
}
......@@ -611,9 +587,8 @@ loop3 = ["loop1", "implied_referenced/from_loop_3"]
assert_eq!(d["not_optional"].crate_name, "actual_pkg");
assert_eq!(d["implied_standalone"].crate_name, "implied_standalone");
assert!(d["implied_standalone"].normal.as_ref().unwrap().is_optional);
assert!(d["a_dep"].normal.is_none());
assert!(d["a_dep"].build.is_some());
assert!(d["implied_standalone"].detail().0.optional());
assert!(d["a_dep"].targets.keys().all(|t| t.kind == Kind::Build));
assert!(!f["implied_standalone"].explicit);
assert!(!f["implied_referenced"].explicit);
assert!(!f["a_dep"].explicit);
......
[workspace.lints.rust]
unsafe = "forbid"
unknown-rule = { level = "allow", priority = -1 }
[lints]
workspace = true
[lints.rust]
unsafe = "allow"
unknown-rule = { level = "forbid" }
use cargo_toml::{Manifest, StripSetting};
use cargo_toml::{Manifest, StripSetting, Lint, LintLevel};
use std::fs::read;
use std::path::Path;
......@@ -103,25 +103,9 @@ fn autoworkspace() {
assert_eq!(workspace.resolver, Some(cargo_toml::Resolver::V2));
}
#[test]
fn legacy() {
let m = Manifest::from_slice(
br#"[project]
name = "foo"
version = "1"
"#,
)
.expect("parse old");
let package = m.package();
assert_eq!("foo", package.name);
let m = Manifest::from_str("name = \"foo\"\nversion=\"1\"").expect("parse bare");
let package = m.package();
assert_eq!("foo", package.name);
}
#[test]
fn proc_macro() {
let manifest = br#"[project]
let manifest = br#"[package]
name = "foo"
version = "1"
[lib]
......@@ -258,3 +242,26 @@ fn unstable() {
assert_eq!("0.0.0", m.package().version());
assert_eq!(false, m.package().publish());
}
#[test]
fn lints() {
let m = Manifest::from_slice(&read("tests/lints/Cargo.toml").unwrap()).unwrap();
let lints = m.workspace.unwrap().lints.unwrap();
let lint_group = lints.get("rust").unwrap();
assert_eq!(lint_group.get("unsafe"), Some(&Lint::Simple(LintLevel::Forbid)));
assert_eq!(lint_group.get("unknown-rule"), Some(&Lint::Detailed{
level: LintLevel::Allow,
priority: Some(-1)
}));
let lints = m.lints.unwrap();
assert_eq!(lints.workspace, true);
let lint_group = lints.groups.get("rust").unwrap();
assert_eq!(lint_group.get("unsafe"), Some(&Lint::Simple(LintLevel::Allow)));
assert_eq!(lint_group.get("unknown-rule"), Some(&Lint::Detailed{
level: LintLevel::Forbid,
priority: None
}));
}