From 56673fee56e62f195cba9de74e8948e5fba03a5c Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Tue, 2 Dec 2025 11:38:51 +0100 Subject: [PATCH] Introduce the first working version of the tool --- Cargo.lock | 1 + crates/xtask/Cargo.toml | 1 + crates/xtask/src/bench/mod.rs | 4 +- crates/xtask/src/bench/workload.rs | 8 +- crates/xtask/src/main.rs | 137 +++++++++++++++++++++++++++-- crates/xtask/src/test/mod.rs | 6 +- crates/xtask/src/test/workload.rs | 4 +- 7 files changed, 143 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7833b420a..9dffcc0a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7806,6 +7806,7 @@ dependencies = [ "futures-core", "futures-util", "reqwest", + "semver", "serde", "serde_json", "sha2", diff --git a/crates/xtask/Cargo.toml b/crates/xtask/Cargo.toml index 0a2cc9e20..7d81cf3f6 100644 --- a/crates/xtask/Cargo.toml +++ b/crates/xtask/Cargo.toml @@ -22,6 +22,7 @@ reqwest = { version = "0.12.24", features = [ "json", "rustls-tls", ], default-features = false } +semver = "1.0.27" serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.145" sha2 = "0.10.9" diff --git a/crates/xtask/src/bench/mod.rs b/crates/xtask/src/bench/mod.rs index 98b901afe..9a0b4e42f 100644 --- a/crates/xtask/src/bench/mod.rs +++ b/crates/xtask/src/bench/mod.rs @@ -23,7 +23,7 @@ pub fn default_dashboard_url() -> String { /// Run benchmarks from a workload #[derive(Parser, Debug)] -pub struct BenchDeriveArgs { +pub struct BenchArgs { /// Common arguments shared with other commands #[command(flatten)] common: CommonArgs, @@ -59,7 +59,7 @@ pub struct BenchDeriveArgs { binary_path: Option, } -pub fn run(args: BenchDeriveArgs) -> anyhow::Result<()> { +pub fn run(args: BenchArgs) -> anyhow::Result<()> { setup_logs(&args.common.log_filter)?; // fetch environment and build info diff --git a/crates/xtask/src/bench/workload.rs b/crates/xtask/src/bench/workload.rs index f8523ef6f..0195f1cc4 100644 --- a/crates/xtask/src/bench/workload.rs +++ b/crates/xtask/src/bench/workload.rs @@ -12,7 +12,7 @@ use tokio::task::JoinHandle; use uuid::Uuid; use super::dashboard::DashboardClient; -use super::BenchDeriveArgs; +use super::BenchArgs; use crate::common::assets::{self, Asset}; use crate::common::client::Client; use crate::common::command::{run_commands, Command}; @@ -40,7 +40,7 @@ async fn run_workload_commands( meili_client: &Arc, workload_uuid: Uuid, workload: &BenchWorkload, - args: &BenchDeriveArgs, + args: &BenchArgs, run_number: u16, ) -> anyhow::Result>> { let report_folder = &args.report_folder; @@ -95,7 +95,7 @@ pub async fn execute( invocation_uuid: Uuid, master_key: Option<&str>, workload: BenchWorkload, - args: &BenchDeriveArgs, + args: &BenchArgs, binary_path: Option<&Path>, ) -> anyhow::Result<()> { assets::fetch_assets(assets_client, &workload.assets, &args.common.asset_folder).await?; @@ -143,7 +143,7 @@ async fn execute_run( workload_uuid: Uuid, master_key: Option<&str>, workload: &BenchWorkload, - args: &BenchDeriveArgs, + args: &BenchArgs, binary_path: Option<&Path>, run_number: u16, ) -> anyhow::Result>> { diff --git a/crates/xtask/src/main.rs b/crates/xtask/src/main.rs index e7c281572..a0b28c57f 100644 --- a/crates/xtask/src/main.rs +++ b/crates/xtask/src/main.rs @@ -1,16 +1,34 @@ -use std::collections::HashSet; +use std::{collections::HashSet, process::Stdio}; +use anyhow::Context; use clap::Parser; -use xtask::{bench::BenchDeriveArgs, test::TestDeriveArgs}; +use semver::{Prerelease, Version}; +use xtask::{bench::BenchArgs, test::TestArgs}; + +/// This is the version of the crate but also the current Meilisearch version +pub const VERSION: &str = env!("CARGO_PKG_VERSION"); /// List features available in the workspace #[derive(Parser, Debug)] -struct ListFeaturesDeriveArgs { +struct ListFeaturesArgs { /// Feature to exclude from the list. Use a comma to separate multiple features. #[arg(short, long, value_delimiter = ',')] exclude_feature: Vec, } +/// Create a git tag for the current version +/// +/// The tag will of the form prototype-v-. +#[derive(Parser, Debug)] +struct PrototypeArgs { + /// Name of the prototype to generate + name: String, + /// If true refuses to increment the tag if it already exists + /// else refuses to generate new tag and expect the tag to exist. + #[arg(long)] + generate_new: bool, +} + /// Utilitary commands #[derive(Parser, Debug)] #[command(author, version, about, long_about)] @@ -18,9 +36,10 @@ struct ListFeaturesDeriveArgs { #[command(bin_name = "cargo xtask")] #[allow(clippy::large_enum_variant)] // please, that's enough... enum Command { - ListFeatures(ListFeaturesDeriveArgs), - Bench(BenchDeriveArgs), - Test(TestDeriveArgs), + ListFeatures(ListFeaturesArgs), + Bench(BenchArgs), + GeneratePrototype(PrototypeArgs), + Test(TestArgs), } fn main() -> anyhow::Result<()> { @@ -28,12 +47,13 @@ fn main() -> anyhow::Result<()> { match args { Command::ListFeatures(args) => list_features(args), Command::Bench(args) => xtask::bench::run(args)?, + Command::GeneratePrototype(args) => generate_prototype(args)?, Command::Test(args) => xtask::test::run(args)?, } Ok(()) } -fn list_features(args: ListFeaturesDeriveArgs) { +fn list_features(args: ListFeaturesArgs) { let exclude_features: HashSet<_> = args.exclude_feature.into_iter().collect(); let metadata = cargo_metadata::MetadataCommand::new().no_deps().exec().unwrap(); let features: Vec = metadata @@ -46,3 +66,106 @@ fn list_features(args: ListFeaturesDeriveArgs) { let features = features.join(" "); println!("{features}") } + +fn generate_prototype(args: PrototypeArgs) -> anyhow::Result<()> { + let PrototypeArgs { name, generate_new: create_new } = args; + + if name.rsplit_once(['.', '-']).filter(|(_, t)| t.chars().all(char::is_numeric)).is_some() { + anyhow::bail!( + "The increment must not be part of the name and will be rather incremented by this command." + ); + } + + // 1. Fetch the crate version + let version = Version::parse(VERSION).context("while semver-parsing the crate version")?; + + // 2. Pull tags from remote and retrieve last prototype tag + std::process::Command::new("git") + .arg("fetch") + .arg("--tags") + .stderr(Stdio::inherit()) + .stdout(Stdio::inherit()) + .status()?; + + let output = std::process::Command::new("git") + .arg("tag") + .args(["--list", "prototype-v*"]) + .stderr(Stdio::inherit()) + .output()?; + let output = + String::try_from(output.stdout).context("while converting the tag list into a string")?; + + let mut highest_increment = None; + for tag in output.lines() { + let Some(version) = tag.strip_prefix("prototype-v") else { + continue; + }; + let Ok(version) = Version::parse(version) else { + continue; + }; + let Ok(proto) = PrototypePrerelease::from_str(version.pre.as_str()) else { + continue; + }; + if proto.name() == name { + highest_increment = match highest_increment { + Some(last) if last < proto.increment() => Some(proto.increment()), + Some(last) => Some(last), + None => Some(proto.increment()), + }; + } + } + + // 3. Generate the new tag name (without git, just a string) + let increment = match (create_new, highest_increment) { + (true, None) => 0, + (true, Some(increment)) => anyhow::bail!( + "A prototype with the name `{name}` already exists with increment `{increment}`" + ), + (false, None) => anyhow::bail!( + "Prototype `{name}` is missing and must exist to be incremented.\n\ + Use the --generate-new flag to create a new prototype with an increment at 0." + ), + (false, Some(increment)) => { + increment.checked_add(1).context("While incrementing by one the increment")? + } + }; + + // Note that we cannot have leading zeros in the increment + let pre = format!("{name}.{increment}").parse().context("while parsing pre-release name")?; + let tag_name = Version { pre, ..version }; + println!("prototype-v{tag_name}"); + + Ok(()) +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct PrototypePrerelease { + pre: Prerelease, +} + +impl PrototypePrerelease { + fn from_str(s: &str) -> anyhow::Result { + Prerelease::new(s) + .map_err(Into::into) + .and_then(|pre| { + if pre.rsplit_once('.').is_some() { + Ok(pre) + } else { + Err(anyhow::anyhow!("Invalid prototype name, missing name or increment")) + } + }) + .map(|pre| PrototypePrerelease { pre }) + } + + fn name(&self) -> &str { + self.pre.rsplit_once('.').expect("Missing prototype name").0 + } + + fn increment(&self) -> u32 { + self.pre + .as_str() + .rsplit_once('.') + .map(|(_, tail)| tail.parse().expect("Invalid increment")) + .expect("Missing increment") + } +} diff --git a/crates/xtask/src/test/mod.rs b/crates/xtask/src/test/mod.rs index de77be42c..82a2ea78a 100644 --- a/crates/xtask/src/test/mod.rs +++ b/crates/xtask/src/test/mod.rs @@ -17,7 +17,7 @@ pub use workload::TestWorkload; /// Run tests from a workload #[derive(Parser, Debug)] -pub struct TestDeriveArgs { +pub struct TestArgs { /// Common arguments shared with other commands #[command(flatten)] common: CommonArgs, @@ -31,7 +31,7 @@ pub struct TestDeriveArgs { pub add_missing_responses: bool, } -pub fn run(args: TestDeriveArgs) -> anyhow::Result<()> { +pub fn run(args: TestArgs) -> anyhow::Result<()> { let rt = tokio::runtime::Builder::new_current_thread().enable_io().enable_time().build()?; let _scope = rt.enter(); @@ -40,7 +40,7 @@ pub fn run(args: TestDeriveArgs) -> anyhow::Result<()> { Ok(()) } -async fn run_inner(args: TestDeriveArgs) -> anyhow::Result<()> { +async fn run_inner(args: TestArgs) -> anyhow::Result<()> { setup_logs(&args.common.log_filter)?; // setup clients diff --git a/crates/xtask/src/test/workload.rs b/crates/xtask/src/test/workload.rs index 62a489499..9e9cf593d 100644 --- a/crates/xtask/src/test/workload.rs +++ b/crates/xtask/src/test/workload.rs @@ -12,7 +12,7 @@ use crate::common::command::{run_commands, Command}; use crate::common::instance::Binary; use crate::common::process::{self, delete_db, kill_meili}; use crate::common::workload::Workload; -use crate::test::TestDeriveArgs; +use crate::test::TestArgs; #[derive(Serialize, Deserialize, Debug)] #[serde(untagged)] @@ -81,7 +81,7 @@ pub struct TestWorkload { impl TestWorkload { pub async fn run( mut self, - args: &TestDeriveArgs, + args: &TestArgs, assets_client: &Client, meili_client: &Arc, asset_folder: &'static str,