From f1577de53bb32e658b60718570fe46f54581f0d0 Mon Sep 17 00:00:00 2001 From: Marek Kaput Date: Wed, 11 Dec 2024 15:03:05 +0100 Subject: [PATCH] Initial commit (#1) * Initial commit * update dependabot.yml * gitignore & Cargo.lock * fix edit_dependencies for missing tables * fix * fix --- .github/CODEOWNERS | 1 + .github/dependabot.yml | 24 +++ .github/workflows/ci.yml | 32 ++++ .gitignore | 22 +-- Cargo.lock | 322 +++++++++++++++++++++++++++++++++++++++ Cargo.toml | 17 +++ LICENSE | 2 +- src/lib.rs | 4 + src/sync_version.rs | 84 ++++++++++ src/upgrade.rs | 285 ++++++++++++++++++++++++++++++++++ 10 files changed, 771 insertions(+), 22 deletions(-) create mode 100644 .github/CODEOWNERS create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/ci.yml create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/lib.rs create mode 100644 src/sync_version.rs create mode 100644 src/upgrade.rs diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..3aa4d4e --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @mkaput diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..757bb37 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,24 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: cargo + directory: "/" + schedule: + interval: monthly + groups: + non critical: + patterns: + - "*" + + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: monthly + groups: + all: + patterns: + - "*" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f7f7481 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,32 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + merge_group: + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - run: cargo fmt --check + - run: cargo clippy --all-targets --all-features -- --no-deps + env: + # Make sure CI fails on all warnings, including Clippy lints. + RUSTFLAGS: "-Dwarnings" + - run: cargo doc --all-features --no-deps + env: + # Make sure CI fails on all warnings, including Clippy lints. + RUSTDOCFLAGS: "-Dwarnings" + - run: cargo test + + udeps: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: bnjbvr/cargo-machete@main diff --git a/.gitignore b/.gitignore index d01bd1a..eb5a316 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1 @@ -# Generated by Cargo -# will have compiled files and executables -debug/ -target/ - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock - -# These are backup files generated by rustfmt -**/*.rs.bk - -# MSVC Windows builds of rustc generate these, which store debugging information -*.pdb - -# RustRover -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ \ No newline at end of file +target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..4f7cd59 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,322 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" + +[[package]] +name = "cairo-toolchain-xtasks" +version = "1.0.0" +dependencies = [ + "anyhow", + "clap", + "semver", + "toml_edit", + "xshell", +] + +[[package]] +name = "clap" +version = "4.5.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "indexmap" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] + +[[package]] +name = "xshell" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e7290c623014758632efe00737145b6867b66292c42167f2ec381eb566a373d" +dependencies = [ + "xshell-macros", +] + +[[package]] +name = "xshell-macros" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32ac00cd3f8ec9c1d33fb3e7958a82df6989c42d747bd326c822b1d625283547" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..c0c48bd --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "cairo-toolchain-xtasks" +version = "1.0.0" +edition = "2021" +rust-version = "1.83.0" + +authors = ["Software Mansion "] +description = "Build scripts shared between all Cairo Toolchain projects maintained by Software Mansion." +license = "MIT" +repository = "https://github.com/software-mansion-labs/cairo-toolchain-xtasks" + +[dependencies] +anyhow = "1" +clap = { version = "4.5", features = ["derive"] } +semver = "1" +toml_edit = "0.22.22" +xshell = "0.2.7" diff --git a/LICENSE b/LICENSE index 320719d..34ac6ec 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Software Mansion Labs +Copyright (c) 2024 Software Mansion S.A. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..defa6e3 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,4 @@ +//! Build scripts shared between all Cairo Toolchain projects maintained by Software Mansion. + +pub mod sync_version; +pub mod upgrade; diff --git a/src/sync_version.rs b/src/sync_version.rs new file mode 100644 index 0000000..49a5504 --- /dev/null +++ b/src/sync_version.rs @@ -0,0 +1,84 @@ +//! Synchronise this crate's version with the `cairo-lang-*` crates. + +use anyhow::{ensure, Result}; +use clap::Parser; +use semver::{Prerelease, Version}; +use toml_edit::{value, DocumentMut}; +use xshell::{cmd, Shell}; + +/// Synchronise this crate's version with the `cairo-lang-*` crates. +#[derive(Default, Parser)] +pub struct Args { + /// Do not edit any files, just inform what would be done. + #[arg(long, default_value_t = false)] + pub dry_run: bool, + + /// Set a custom value for the `build` metadata. + #[arg(long)] + pub build: Option, + + /// Clear the pre-release identifier from the version. + #[arg(long, default_value_t = false)] + pub no_pre_release: bool, +} + +pub fn main(args: Args) -> Result<()> { + let sh = Shell::new()?; + + let mut cargo_toml = sh.read_file("Cargo.toml")?.parse::()?; + + let (package, table_path) = if let Some(workspace_package) = cargo_toml + .get_mut("workspace") + .and_then(|t| t.get_mut("package")) + .and_then(|t| t.as_table_mut()) + { + (workspace_package, "workspace.package") + } else { + (cargo_toml["package"].as_table_mut().unwrap(), "package") + }; + + let mut version = expected_version()?; + + if let Some(build) = args.build { + version.build = build.parse()?; + } + if args.no_pre_release { + version.pre = Prerelease::EMPTY; + } + + package["version"] = value(version.to_string()); + + eprintln!("[{table_path}]\n{package}"); + + if !args.dry_run { + sh.write_file("Cargo.toml", cargo_toml.to_string())?; + + cmd!(sh, "cargo fetch").run()?; + } + + Ok(()) +} + +/// Gets the version of the `cairo-lang-compiler` crate from `Cargo.lock`, which is the expected +/// version for the crate this script is being run on. +pub fn expected_version() -> Result { + // NOTE: We are deliberately not using cargo_metadata to reduce build times of xtasks. + + let sh = Shell::new()?; + let cargo_lock = sh.read_file("Cargo.lock")?.parse::()?; + let packages = cargo_lock["package"].as_array_of_tables().unwrap(); + let compiler = { + let pkgs = packages + .into_iter() + .filter(|pkg| pkg["name"].as_str().unwrap() == "cairo-lang-compiler") + .collect::>(); + ensure!( + pkgs.len() == 1, + "expected exactly one cairo-lang-compiler package in Cargo.lock, found: {}", + pkgs.len() + ); + pkgs.into_iter().next().unwrap() + }; + let compiler_version = compiler["version"].as_str().unwrap(); + Ok(compiler_version.parse()?) +} diff --git a/src/upgrade.rs b/src/upgrade.rs new file mode 100644 index 0000000..f06c61b --- /dev/null +++ b/src/upgrade.rs @@ -0,0 +1,285 @@ +//! Update toolchain crates properly. + +use anyhow::Result; +use clap::{Parser, ValueEnum}; +use semver::Version; +use std::mem; +use std::path::PathBuf; +use toml_edit::{DocumentMut, InlineTable, Value}; +use xshell::{cmd, Shell}; + +/// Update toolchain crates properly. +#[derive(Parser)] +pub struct Args { + /// Name of toolchain dependency (group) to update. + dep: DepName, + + #[command(flatten)] + spec: Spec, + + /// Do not edit any files, just inform what would be done. + #[arg(long, default_value_t = false)] + dry_run: bool, +} + +#[derive(ValueEnum, Copy, Clone, Debug)] +enum DepName { + Cairo, + #[value(name = "cairols")] + CairoLS, +} + +#[derive(clap::Args, Clone)] +#[group(required = true, multiple = true)] +struct Spec { + /// Source the dependency from crates.io and use a specific version. + version: Option, + + /// Source the dependency from the GitHub repository and use a specific commit/ref. + #[arg(short, long, conflicts_with = "branch")] + rev: Option, + + /// Source the dependency from the GitHub repository and use a specific branch. + #[arg(short, long)] + branch: Option, + + /// Source the dependency from a local filesystem. + /// + /// This is useful for local development, but avoid commiting this to the repository. + #[arg(short, long, conflicts_with_all = ["rev", "branch"])] + path: Option, +} + +pub fn main(args: Args) -> Result<()> { + let sh = Shell::new()?; + + let mut cargo_toml = sh.read_file("Cargo.toml")?.parse::()?; + + edit_dependencies(&mut cargo_toml, "dependencies", &args); + edit_dependencies(&mut cargo_toml, "dev-dependencies", &args); + edit_dependencies(&mut cargo_toml, "workspace.dependencies", &args); + edit_patch(&mut cargo_toml, &args); + + if !args.dry_run { + sh.write_file("Cargo.toml", cargo_toml.to_string())?; + + cmd!(sh, "cargo fetch").run()?; + + purge_unused_patches(&mut cargo_toml)?; + sh.write_file("Cargo.toml", cargo_toml.to_string())?; + + cmd!(sh, "cargo xtask sync-version").run()?; + } + + Ok(()) +} + +fn edit_dependencies(cargo_toml: &mut DocumentMut, table_path: &str, args: &Args) { + let Some(deps) = table_path + .split('.') + .try_fold(cargo_toml.as_item_mut(), |doc, key| doc.get_mut(key)) + else { + return; + }; + let deps = deps.as_table_mut().unwrap(); + + for (_, dep) in deps.iter_mut().filter(|(key, _)| args.dep.owns(key)) { + let dep = dep.as_value_mut().unwrap(); + + // Always use crates.io requirements so that we can reliably patch them with the + // `[patch.crates-io]` table. + let mut new_dep = InlineTable::from_iter([( + "version", + match &args.spec.version { + Some(version) => Value::from(version.to_string()), + None => Value::from("*"), + }, + )]); + + copy_dependency_features(&mut new_dep, dep); + + *dep = new_dep.into(); + simplify_dependency_table(dep) + } + + deps.fmt(); + deps.sort_values(); + + eprintln!("[{table_path}]"); + for (key, dep) in deps.iter().filter(|(key, _)| args.dep.owns(key)) { + eprintln!("{key} = {dep}"); + } +} + +fn edit_patch(cargo_toml: &mut DocumentMut, args: &Args) { + let patch = cargo_toml["patch"].as_table_mut().unwrap()["crates-io"] + .as_table_mut() + .unwrap(); + + // Clear any existing entries for this dependency. + for crate_name in args.dep.crates() { + patch.remove(crate_name); + } + + // Leave this section as-if if we are requested to just use a specific version. + if args.spec.rev.is_some() || args.spec.branch.is_some() || args.spec.path.is_some() { + // Patch all Cairo crates that exist, even if this project does not directly depend on them, + // to avoid any duplicates in transient dependencies. + for &dep_name in args.dep.crates() { + let mut dep = InlineTable::new(); + + // Add a Git branch or revision reference if requested. + if args.spec.rev.is_some() || args.spec.branch.is_some() { + dep.insert("git", args.dep.repo().into()); + } + + if let Some(branch) = &args.spec.branch { + dep.insert("branch", branch.as_str().into()); + } + + if let Some(rev) = &args.spec.rev { + dep.insert("rev", rev.as_str().into()); + } + + // Add local path reference if requested. + // For local path sources, Cargo is not looking for crates recursively therefore, we + // need to manually provide full paths to Cairo workspace member crates. + if let Some(path) = &args.spec.path { + dep.insert( + "path", + path.join("crates") + .join(dep_name) + .to_string_lossy() + .into_owned() + .into(), + ); + } + + patch.insert(dep_name, dep.into()); + } + } + + patch.fmt(); + patch.sort_values(); + + eprintln!("[patch.crates-io]"); + for (key, dep) in patch.iter() { + eprintln!("{key} = {dep}"); + } +} + +impl DepName { + fn crates(&self) -> &'static [&'static str] { + match self { + DepName::Cairo => { + // List of library crates published from the starkware-libs/cairo repository. + // One can get this list from the `scripts/release_crates.sh` script in that repo. + // Keep this list sorted for better commit diffs. + &[ + "cairo-lang-casm", + "cairo-lang-compiler", + "cairo-lang-debug", + "cairo-lang-defs", + "cairo-lang-diagnostics", + "cairo-lang-doc", + "cairo-lang-eq-solver", + "cairo-lang-executable", + "cairo-lang-filesystem", + "cairo-lang-formatter", + "cairo-lang-lowering", + "cairo-lang-parser", + "cairo-lang-plugins", + "cairo-lang-proc-macros", + "cairo-lang-project", + "cairo-lang-runnable-utils", + "cairo-lang-runner", + "cairo-lang-semantic", + "cairo-lang-sierra", + "cairo-lang-sierra-ap-change", + "cairo-lang-sierra-gas", + "cairo-lang-sierra-generator", + "cairo-lang-sierra-to-casm", + "cairo-lang-sierra-type-size", + "cairo-lang-starknet", + "cairo-lang-starknet-classes", + "cairo-lang-syntax", + "cairo-lang-syntax-codegen", + "cairo-lang-test-plugin", + "cairo-lang-test-runner", + "cairo-lang-test-utils", + "cairo-lang-utils", + ] + } + DepName::CairoLS => &["cairo-language-server"], + } + } + + fn owns(&self, crate_name: &str) -> bool { + self.crates().contains(&crate_name) + } + + fn repo(&self) -> &'static str { + match self { + DepName::Cairo => "https://github.com/starkware-libs/cairo", + DepName::CairoLS => "https://github.com/software-mansion/cairols", + } + } +} + +/// Copies features from source dependency spec to new dependency table, if exists. +fn copy_dependency_features(dest: &mut InlineTable, src: &Value) { + if let Some(dep) = src.as_inline_table() { + if let Some(features) = dep.get("features") { + dest.insert("features", features.clone()); + } + } +} + +/// Simplifies a `{ version = "V" }` dependency spec to shorthand `"V"` if possible. +fn simplify_dependency_table(dep: &mut Value) { + *dep = match mem::replace(dep, false.into()) { + Value::InlineTable(mut table) => { + if table.len() == 1 { + table.remove("version").unwrap_or_else(|| table.into()) + } else { + table.into() + } + } + + dep => dep, + } +} + +/// Remove any unused patches from the `[patch.crates-io]` table. +/// +/// We are adding patch entries for **all** Cairo crates existing, and some may end up being unused. +/// Cargo is emitting warnings about unused patches and keeps a record of them in the `Cargo.lock`. +/// The goal of this function is to resolve these warnings. +fn purge_unused_patches(cargo_toml: &mut DocumentMut) -> Result<()> { + let sh = Shell::new()?; + let cargo_lock = sh.read_file("Cargo.lock")?.parse::()?; + + if let Some(unused_patches) = find_unused_patches(&cargo_lock) { + let patch = cargo_toml["patch"].as_table_mut().unwrap()["crates-io"] + .as_table_mut() + .unwrap(); + + // Remove any patches that are not for Cairo crates. + patch.retain(|key, _| !unused_patches.contains(&key.to_owned())); + } + + Ok(()) +} + +/// Extracts names of unused patches from the `[[patch.unused]]` array from the `Cargo.lock` file. +fn find_unused_patches(cargo_lock: &DocumentMut) -> Option> { + Some( + cargo_lock + .get("patch")? + .get("unused")? + .as_array_of_tables()? + .iter() + .flat_map(|table| Some(table.get("name")?.as_str()?.to_owned())) + .collect(), + ) +}