diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 00d2c89..8a69316 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ on: permissions: contents: read jobs: - rust: + stable: name: Rust Job runs-on: ubuntu-latest steps: @@ -25,6 +25,16 @@ jobs: toolchain: stable - name: Run Cargo Test run: RUSTFLAGS="-Awarnings" cargo test --all-features --workspace + nightly: + name: Rust Job + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + - name: Setup Rust Toolchain + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: nightly - name: Check Formatting run: cargo fmt -- --check - name: Run Clippy diff --git a/workspace/gh-workflow-gen/build.rs b/workspace/gh-workflow-gen/build.rs index c415c2d..f66dfb0 100644 --- a/workspace/gh-workflow-gen/build.rs +++ b/workspace/gh-workflow-gen/build.rs @@ -1,12 +1,27 @@ -use gh_workflow::toolchain::RustToolchain; +use gh_workflow::toolchain::Toolchain; +use gh_workflow::{Permissions, Workflow}; fn main() { - let toolchain = RustToolchain::default() - .workspace(true) - .fmt(true) - .clippy(true); - toolchain - .to_workflow() + let stable = Toolchain::stable().workspace(true).test(true); + + let nightly = Toolchain::nightly().workspace(true).fmt(true).clippy(true); + + Workflow::new("CI") + .permissions(Permissions::read()) + .on(vec![ + // TODO: enums + ("push", vec![("branches", vec!["main"])]), + ( + "pull_request", + vec![ + ("types", vec!["opened", "synchronize", "reopened"]), + ("branches", vec!["main"]), + ], + ), + ]) + .add_job("stable", stable) + .unwrap() + .add_job("nightly", nightly) .unwrap() .write(format!( "{}/../../.github/workflows/ci.yml", diff --git a/workspace/gh-workflow/src/toolchain.rs b/workspace/gh-workflow/src/toolchain.rs index 3b8f638..5eddc78 100644 --- a/workspace/gh-workflow/src/toolchain.rs +++ b/workspace/gh-workflow/src/toolchain.rs @@ -1,51 +1,76 @@ +//! A type-safe representation of the Rust toolchain. use std::time::Duration; use derive_setters::Setters; -use crate::error::Result; use crate::workflow::*; -/// -/// A type-safe representation of the Rust toolchain. -/// Instead of writing the github action for Rust by hand, we can use this -/// struct to generate the github action. +pub trait Version: ToString {} + #[derive(Default, Clone)] -pub enum Version { - #[default] - Stable, - Beta, - Nightly, +pub struct Stable; + +impl Version for Stable {} +impl ToString for Stable { + fn to_string(&self) -> String { + "stable".to_string() + } } -impl ToString for Version { +#[derive(Default, Clone)] +pub struct Nightly; + +impl Version for Nightly {} +impl ToString for Nightly { fn to_string(&self) -> String { - match self { - Version::Stable => "stable".to_string(), - Version::Beta => "beta".to_string(), - Version::Nightly => "nightly".to_string(), - } + "nightly".to_string() } } -#[derive(Setters, Default, Clone)] -#[setters(strip_option)] -pub struct RustToolchain { +#[derive(Setters, Clone)] +pub struct Toolchain { version: Version, fmt: bool, clippy: bool, timeout: Option, workspace: bool, + test: bool, } -impl RustToolchain { - pub fn to_workflow(&self) -> Result { +impl Toolchain { + pub fn new(version: V) -> Self { + Toolchain { + version, + fmt: false, + clippy: false, + timeout: None, + workspace: false, + test: false, + } + } +} + +impl Toolchain { + pub fn stable() -> Self { + Toolchain::new(Stable) + } +} + +impl Toolchain { + pub fn nightly() -> Self { + Toolchain::new(Nightly) + } +} + +impl Into for Toolchain { + fn into(self) -> Job { let mut job = Job::new("Rust Job") .runs_on("ubuntu-latest") .add_step(Step::uses("actions", "checkout", 4).name("Checkout Code")) .add_step( Step::uses("actions-rust-lang", "setup-rust-toolchain", 1) .name("Setup Rust Toolchain") - .with(("toolchain", self.version.clone())), + .with(("toolchain", self.version)), ); if let Some(timeout) = self.timeout { @@ -58,13 +83,15 @@ impl RustToolchain { cargo_test_args.push("--workspace"); } - job = job.add_step( - Step::run(format!( - "RUSTFLAGS=\"-Awarnings\" cargo test {}", - cargo_test_args.join(" ") - )) - .name("Run Cargo Test"), - ); + if self.test { + job = job.add_step( + Step::run(format!( + "RUSTFLAGS=\"-Awarnings\" cargo test {}", + cargo_test_args.join(" ") + )) + .name("Run Cargo Test"), + ); + } if self.fmt { job = job.add_step(Step::run("cargo fmt -- --check").name("Check formatting")); @@ -74,19 +101,6 @@ impl RustToolchain { job = job.add_step(Step::run("cargo clippy -- -D warnings").name("Run clippy")); } - Workflow::new("CI") - .permissions(Permissions::read()) - .on(vec![ - // TODO: enums - ("push", vec![("branches", vec!["main"])]), - ( - "pull_request", - vec![ - ("types", vec!["opened", "synchronize", "reopened"]), - ("branches", vec!["main"]), - ], - ), - ]) - .add_job("rust", job) + job } } diff --git a/workspace/gh-workflow/src/workflow.rs b/workspace/gh-workflow/src/workflow.rs index 3a58f93..7905b85 100644 --- a/workspace/gh-workflow/src/workflow.rs +++ b/workspace/gh-workflow/src/workflow.rs @@ -1,10 +1,9 @@ -use std::{path::Path, time::Duration}; - use convert_case::{Case, Casing}; use derive_setters::Setters; use indexmap::IndexMap; use serde::{Deserialize, Serialize}; use serde_json::Value; +use std::{path::Path, time::Duration}; use crate::error::{Error, Result}; @@ -48,6 +47,20 @@ pub enum Event { RepositoryDispatch, } +#[derive(Debug, Serialize, Deserialize, Clone, Hash, PartialEq, Eq)] +#[serde(transparent)] +pub struct JobKey(String); + +impl JobKey { + fn new(key: T) -> Self { + Self(key.to_string().to_case(Case::Kebab)) + } + + fn as_str(&self) -> &str { + &self.0 + } +} + #[derive(Debug, Default, Setters, Serialize, Deserialize, Clone, PartialEq, Eq)] #[serde(rename_all = "kebab-case")] #[setters(strip_option)] @@ -62,7 +75,7 @@ pub struct Workflow { #[serde(skip_serializing_if = "Option::is_none")] pub permissions: Option, #[serde(skip_serializing_if = "IndexMap::is_empty")] - pub jobs: IndexMap, + pub jobs: IndexMap, #[serde(skip_serializing_if = "Option::is_none")] pub concurrency: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -93,12 +106,13 @@ impl Workflow { Ok(serde_yaml::to_string(self)?) } - pub fn add_job(mut self, id: T, job: Job) -> Result { - if self.jobs.contains_key(id.to_string().as_str()) { - return Err(Error::JobIdAlreadyExists(id.to_string())); + pub fn add_job>(mut self, id: T, job: J) -> Result { + let key = JobKey::new(id); + if self.jobs.contains_key(&key) { + return Err(Error::JobIdAlreadyExists(key.as_str().to_string())); } - self.jobs.insert(id.to_string(), job); + self.jobs.insert(key, job.into()); Ok(self) }