blob: 3117380fcc5f9b3906fb3eb086a1f6e2952fd382 [file] [log] [blame]
// 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(),
}
}