123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129 |
- use std::{
- fmt::Write as _,
- fs::{read_to_string, write},
- path::Path,
- };
- use anyhow::{bail, Context as _};
- use cargo_metadata::{Metadata, Package};
- use clap::Parser;
- use dialoguer::{theme::ColorfulTheme, Confirm};
- use diff::{lines, Result as Diff};
- use thiserror::Error;
- #[derive(Debug, Parser)]
- pub struct Options {
- /// Bless new API changes.
- #[clap(long)]
- pub bless: bool,
- }
- #[derive(Error, Debug)]
- enum PublicApiError {
- #[error("error checking public api for {package}\n{source}\n")]
- Error {
- package: String,
- source: anyhow::Error,
- },
- #[error("public api for {package} changed:\n{diff}\n")]
- Changed { package: String, diff: String },
- }
- pub fn public_api(options: Options, metadata: Metadata) -> anyhow::Result<()> {
- let toolchain = "nightly";
- let Options { bless } = options;
- if !rustup_toolchain::is_installed(toolchain)? {
- if Confirm::with_theme(&ColorfulTheme::default())
- .with_prompt("No nightly toolchain detected. Would you like to install one?")
- .interact()?
- {
- rustup_toolchain::install(toolchain)?;
- } else {
- bail!("nightly toolchain not installed")
- }
- }
- let Metadata {
- workspace_root,
- packages,
- ..
- } = &metadata;
- let mut buf = String::new();
- packages.iter().for_each(|Package { name, publish, .. }| {
- if matches!(publish, Some(publish) if publish.is_empty()) {
- return;
- }
- if let Err(e) = check_package_api(name, toolchain, bless, workspace_root.as_std_path()) {
- write!(&mut buf, "{}", e).unwrap();
- }
- });
- if !buf.is_empty() {
- bail!("public api may have changed in one or more packages.\nplease bless by re-running this command with --bless\nErrors:\n{buf}");
- }
- Ok(())
- }
- fn check_package_api(
- package: &str,
- toolchain: &str,
- bless: bool,
- workspace_root: &Path,
- ) -> Result<(), PublicApiError> {
- let path = workspace_root
- .join("xtask")
- .join("public-api")
- .join(package)
- .with_extension("txt");
- let rustdoc_json = rustdoc_json::Builder::default()
- .toolchain(toolchain)
- .package(package)
- .all_features(true)
- .build()
- .map_err(|source| PublicApiError::Error {
- package: package.to_string(),
- source: source.into(),
- })?;
- let public_api = public_api::Builder::from_rustdoc_json(rustdoc_json)
- .build()
- .map_err(|source| PublicApiError::Error {
- package: package.to_string(),
- source: source.into(),
- })?;
- if bless {
- write(&path, public_api.to_string().as_bytes()).map_err(|source| {
- PublicApiError::Error {
- package: package.to_string(),
- source: source.into(),
- }
- })?;
- }
- let current_api = read_to_string(&path)
- .with_context(|| format!("error reading {}", &path.display()))
- .map_err(|source| PublicApiError::Error {
- package: package.to_string(),
- source,
- })?;
- let mut buf = String::new();
- lines(¤t_api, &public_api.to_string())
- .into_iter()
- .for_each(|diff| match diff {
- Diff::Both(..) => (),
- Diff::Right(line) => writeln!(&mut buf, "-{}", line).unwrap(),
- Diff::Left(line) => writeln!(&mut buf, "+{}", line).unwrap(),
- });
- if !buf.is_empty() {
- return Err(PublicApiError::Changed {
- package: package.to_string(),
- diff: buf,
- });
- };
- Ok(())
- }
|