// 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; /// 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; /// 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, #[serde(flatten)] pub dependency_spec: DependencySpec, } #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] pub struct WorkspaceSpec { pub members: Vec, } /// 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, /// Required features. #[serde(default, skip_serializing_if = "Vec::is_empty")] pub features: Vec, /// 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, } /// 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, #[serde(flatten)] pub dependency_spec: DependencySpec, #[serde(default, rename = "patch")] pub patches: BTreeMap, } #[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, #[serde(default)] pub edition: Edition, #[serde(default, skip_serializing_if = "Option::is_none")] pub description: Option, #[serde(default, skip_serializing_if = "String::is_empty")] pub license: 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>( 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, license: "".to_string(), }; CargoManifest { package, workspace, dependency_spec, patches: std::iter::once(("crates-io".to_string(), patch_sections)).collect(), } }