| // Copyright 2022 The Chromium Authors. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| //! Utilities for parsing and generating Cargo.toml and related manifest files. |
| |
| use std::collections::BTreeMap; |
| use std::path::PathBuf; |
| |
| use serde::{Deserialize, Serialize}; |
| |
| /// Set of dependencies for a particular usage: final artifacts, tests, or |
| /// build scripts. |
| pub type DependencySet = BTreeMap<String, Dependency>; |
| /// Set of patches to replace upstream dependencies with local crates. Maps |
| /// arbitrary patch names to `CargoPatch` which includes the actual package name |
| /// and the local path. |
| pub type CargoPatchSet = BTreeMap<String, CargoPatch>; |
| |
| /// A specific crate version. |
| pub use semver::Version; |
| |
| /// A version constraint in a dependency spec. We don't use `semver::VersionReq` |
| /// since we only pass it through opaquely from third_party.toml to Cargo.toml. |
| /// Parsing it is unnecessary. |
| #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] |
| // From serde's perspective we serialize and deserialize this as a plain string. |
| #[serde(transparent)] |
| pub struct VersionConstraint(pub String); |
| |
| /// Parsed third_party.toml. This is a limited variant of Cargo.toml. |
| #[derive(Clone, Debug, Deserialize)] |
| pub struct ThirdPartyManifest { |
| #[serde(default, skip_serializing_if = "Option::is_none")] |
| pub workspace: Option<WorkspaceSpec>, |
| #[serde(flatten)] |
| pub dependency_spec: DependencySpec, |
| } |
| |
| #[derive(Clone, Debug, Deserialize, Serialize)] |
| #[serde(rename_all = "kebab-case")] |
| pub struct WorkspaceSpec { |
| pub members: Vec<String>, |
| } |
| |
| /// The sets of all types of dependencies for a manifest: regular, build script, |
| /// and test. This should be included in other structs with `#[serde(flatten)]` |
| #[derive(Clone, Debug, Deserialize, Serialize)] |
| #[serde(rename_all = "kebab-case")] |
| pub struct DependencySpec { |
| /// Regular dependencies built into production code. |
| #[serde( |
| default, |
| skip_serializing_if = "DependencySet::is_empty", |
| serialize_with = "toml::ser::tables_last" |
| )] |
| pub dependencies: DependencySet, |
| /// Test-only dependencies. |
| #[serde( |
| default, |
| skip_serializing_if = "DependencySet::is_empty", |
| serialize_with = "toml::ser::tables_last" |
| )] |
| pub dev_dependencies: DependencySet, |
| /// Build script dependencies. |
| #[serde( |
| default, |
| skip_serializing_if = "DependencySet::is_empty", |
| serialize_with = "toml::ser::tables_last" |
| )] |
| pub build_dependencies: DependencySet, |
| } |
| |
| /// A single crate dependency. |
| #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] |
| #[serde(untagged)] |
| pub enum Dependency { |
| /// A dependency of the form `foo = "1.0.11"`: just the package name as key |
| /// and the version as value. The sole field is the crate version. |
| Short(VersionConstraint), |
| /// A dependency that specifies other fields in the form of `foo = { ... }` |
| /// or `[dependencies.foo] ... `. |
| Full(FullDependency), |
| } |
| |
| /// A single crate dependency with some extra fields from third_party.toml. |
| #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] |
| #[serde(rename_all = "kebab-case")] |
| pub struct FullDependency { |
| /// Version constraint on dependency. |
| #[serde(default, skip_serializing_if = "Option::is_none")] |
| pub version: Option<VersionConstraint>, |
| /// Required features. |
| #[serde(default, skip_serializing_if = "Vec::is_empty")] |
| pub features: Vec<String>, |
| /// Whether this can be used directly from Chromium code, or only from other |
| /// third-party crates. |
| #[serde(default = "get_true", skip_serializing_if = "is_true")] |
| pub allow_first_party_usage: bool, |
| /// List of files generated by build.rs script. |
| #[serde(default, skip_serializing_if = "Vec::is_empty")] |
| pub build_script_outputs: Vec<String>, |
| } |
| |
| /// Representation of a Cargo.toml file. |
| #[derive(Clone, Debug, Deserialize, Serialize)] |
| #[serde(rename_all = "kebab-case")] |
| pub struct CargoManifest { |
| pub package: CargoPackage, |
| #[serde(default, skip_serializing_if = "Option::is_none")] |
| pub workspace: Option<WorkspaceSpec>, |
| #[serde(flatten)] |
| pub dependency_spec: DependencySpec, |
| #[serde(default, rename = "patch")] |
| pub patches: BTreeMap<String, CargoPatchSet>, |
| } |
| |
| #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] |
| #[serde(rename_all = "kebab-case")] |
| pub struct CargoPackage { |
| pub name: String, |
| pub version: Version, |
| #[serde(default, skip_serializing_if = "Vec::is_empty")] |
| pub authors: Vec<String>, |
| #[serde(default)] |
| pub edition: Edition, |
| #[serde(default, skip_serializing_if = "Option::is_none")] |
| pub description: Option<String>, |
| } |
| |
| #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] |
| #[serde(transparent)] |
| pub struct Edition(pub String); |
| |
| impl Default for Edition { |
| fn default() -> Self { |
| Edition("2015".to_string()) |
| } |
| } |
| |
| #[derive(Clone, Debug, Deserialize, Serialize)] |
| #[serde(rename_all = "kebab-case")] |
| pub struct CargoPatch { |
| pub path: String, |
| pub package: String, |
| } |
| |
| // Used to set the serde default of a field to true. |
| fn get_true() -> bool { |
| true |
| } |
| |
| fn is_true(b: &bool) -> bool { |
| *b |
| } |
| |
| #[derive(Debug)] |
| pub struct PatchSpecification { |
| pub package_name: String, |
| pub patch_name: String, |
| pub path: PathBuf, |
| } |
| |
| pub fn generate_fake_cargo_toml<Iter: IntoIterator<Item = PatchSpecification>>( |
| third_party_manifest: ThirdPartyManifest, |
| patches: Iter, |
| ) -> CargoManifest { |
| let ThirdPartyManifest { workspace, mut dependency_spec } = third_party_manifest; |
| |
| // Hack: set all `allow_first_party_usage` fields to true so they are |
| // suppressed in the Cargo.toml. |
| for dep in [ |
| dependency_spec.dependencies.values_mut(), |
| dependency_spec.build_dependencies.values_mut(), |
| dependency_spec.dev_dependencies.values_mut(), |
| ] |
| .into_iter() |
| .flatten() |
| { |
| if let Dependency::Full(ref mut dep) = dep { |
| dep.allow_first_party_usage = true; |
| } |
| } |
| |
| let mut patch_sections = CargoPatchSet::new(); |
| // Generate patch section. |
| for PatchSpecification { package_name, patch_name, path } in patches { |
| patch_sections.insert( |
| patch_name, |
| CargoPatch { path: path.to_str().unwrap().to_string(), package: package_name }, |
| ); |
| } |
| |
| let package = CargoPackage { |
| name: "chromium".to_string(), |
| version: Version::new(0, 1, 0), |
| authors: Vec::new(), |
| edition: Edition("2021".to_string()), |
| description: None, |
| }; |
| |
| CargoManifest { |
| package, |
| workspace, |
| dependency_spec, |
| patches: std::iter::once(("crates-io".to_string(), patch_sections)).collect(), |
| } |
| } |