workflows.rs

  1use anyhow::{Context, Result};
  2use clap::Parser;
  3use gh_workflow::Workflow;
  4use std::fs;
  5use std::path::{Path, PathBuf};
  6
  7mod after_release;
  8mod autofix_pr;
  9mod bump_patch_version;
 10mod cherry_pick;
 11mod compare_perf;
 12mod danger;
 13mod extension_bump;
 14mod extension_release;
 15mod extension_tests;
 16mod extension_workflow_rollout;
 17mod extensions;
 18mod nix_build;
 19mod release_nightly;
 20mod run_bundling;
 21
 22mod release;
 23mod run_agent_evals;
 24mod run_tests;
 25mod runners;
 26mod steps;
 27mod vars;
 28
 29#[derive(Parser)]
 30pub struct GenerateWorkflowArgs {}
 31
 32struct WorkflowFile {
 33    source: fn() -> Workflow,
 34    r#type: WorkflowType,
 35}
 36
 37impl WorkflowFile {
 38    fn zed(f: fn() -> Workflow) -> WorkflowFile {
 39        WorkflowFile {
 40            source: f,
 41            r#type: WorkflowType::Zed,
 42        }
 43    }
 44
 45    fn extension(f: fn() -> Workflow) -> WorkflowFile {
 46        WorkflowFile {
 47            source: f,
 48            r#type: WorkflowType::ExtensionCI,
 49        }
 50    }
 51
 52    fn extension_shared(f: fn() -> Workflow) -> WorkflowFile {
 53        WorkflowFile {
 54            source: f,
 55            r#type: WorkflowType::ExtensionsShared,
 56        }
 57    }
 58
 59    fn generate_file(&self) -> Result<()> {
 60        let workflow = (self.source)();
 61        let workflow_folder = self.r#type.folder_path();
 62
 63        fs::create_dir_all(&workflow_folder).with_context(|| {
 64            format!("Failed to create directory: {}", workflow_folder.display())
 65        })?;
 66
 67        let workflow_name = workflow
 68            .name
 69            .as_ref()
 70            .expect("Workflow must have a name at this point");
 71        let filename = format!(
 72            "{}.yml",
 73            workflow_name.rsplit("::").next().unwrap_or(workflow_name)
 74        );
 75
 76        let workflow_path = workflow_folder.join(filename);
 77
 78        let content = workflow
 79            .to_string()
 80            .map_err(|e| anyhow::anyhow!("{:?}: {:?}", workflow_path, e))?;
 81
 82        let disclaimer = self.r#type.disclaimer(workflow_name);
 83
 84        let content = [disclaimer, content].join("\n");
 85        fs::write(&workflow_path, content).map_err(Into::into)
 86    }
 87}
 88
 89#[derive(PartialEq, Eq)]
 90enum WorkflowType {
 91    /// Workflows living in the Zed repository
 92    Zed,
 93    /// Workflows living in the `zed-extensions/workflows` repository that are
 94    /// required workflows for PRs to the extension organization
 95    ExtensionCI,
 96    /// Workflows living in each of the extensions to perform checks and version
 97    /// bumps until a better, more centralized system for that is in place.
 98    ExtensionsShared,
 99}
100
101impl WorkflowType {
102    fn disclaimer(&self, workflow_name: &str) -> String {
103        format!(
104            concat!(
105                "# Generated from xtask::workflows::{}{}\n",
106                "# Rebuild with `cargo xtask workflows`.",
107            ),
108            workflow_name,
109            (*self != WorkflowType::Zed)
110                .then_some(" within the Zed repository.")
111                .unwrap_or_default(),
112        )
113    }
114
115    fn folder_path(&self) -> PathBuf {
116        match self {
117            WorkflowType::Zed => PathBuf::from(".github/workflows"),
118            WorkflowType::ExtensionCI => PathBuf::from("extensions/workflows"),
119            WorkflowType::ExtensionsShared => PathBuf::from("extensions/workflows/shared"),
120        }
121    }
122}
123
124pub fn run_workflows(_: GenerateWorkflowArgs) -> Result<()> {
125    if !Path::new("crates/zed/").is_dir() {
126        anyhow::bail!("xtask workflows must be ran from the project root");
127    }
128
129    let workflows = [
130        WorkflowFile::zed(after_release::after_release),
131        WorkflowFile::zed(autofix_pr::autofix_pr),
132        WorkflowFile::zed(bump_patch_version::bump_patch_version),
133        WorkflowFile::zed(cherry_pick::cherry_pick),
134        WorkflowFile::zed(compare_perf::compare_perf),
135        WorkflowFile::zed(danger::danger),
136        WorkflowFile::zed(extension_bump::extension_bump),
137        WorkflowFile::zed(extension_release::extension_release),
138        WorkflowFile::zed(extension_tests::extension_tests),
139        WorkflowFile::zed(extension_workflow_rollout::extension_workflow_rollout),
140        WorkflowFile::zed(release::release),
141        WorkflowFile::zed(release_nightly::release_nightly),
142        WorkflowFile::zed(run_agent_evals::run_agent_evals),
143        WorkflowFile::zed(run_agent_evals::run_cron_unit_evals),
144        WorkflowFile::zed(run_agent_evals::run_unit_evals),
145        WorkflowFile::zed(run_bundling::run_bundling),
146        WorkflowFile::zed(run_tests::run_tests),
147        /* workflows used for CI/CD in extension repositories */
148        WorkflowFile::extension(extensions::run_tests::run_tests),
149        WorkflowFile::extension_shared(extensions::bump_version::bump_version),
150        WorkflowFile::extension_shared(extensions::release_version::release_version),
151    ];
152
153    for workflow_file in workflows {
154        workflow_file.generate_file()?;
155    }
156
157    Ok(())
158}