224 lines · 7.1 KB · Rust
RawDownload
use anyhow::{Context, Result};use clap::Parser;use gh_workflow::Workflow;use std::fs;use std::path::{Path, PathBuf}; use crate::tasks::workflow_checks::{self}; mod after_release;mod autofix_pr;mod bump_patch_version;mod cherry_pick;mod compare_perf;mod danger;mod deploy_collab;mod extension_auto_bump;mod extension_bump;mod extension_tests;mod extension_workflow_rollout;mod extensions;mod nix_build;mod publish_extension_cli;mod release_nightly;mod run_bundling; mod release;mod run_agent_evals;mod run_tests;mod runners;mod steps;mod vars; #[derive(Clone)]pub(crate) struct GitSha(String); impl AsRef<str> for GitSha {    fn as_ref(&self) -> &str {        &self.0    }} #[allow(    clippy::disallowed_methods,    reason = "This runs only in a CLI environment")]fn parse_ref(value: &str) -> Result<GitSha, String> {    const GIT_SHA_LENGTH: usize = 40;    (value.len() == GIT_SHA_LENGTH)        .then_some(value)        .ok_or_else(|| {            format!(                "Git SHA has wrong length! \                Only SHAs with a full length of {GIT_SHA_LENGTH} are supported, found {len} characters.",                len = value.len()            )        })        .and_then(|value| {            let mut tmp = [0; 4];            value                .chars()                .all(|char| u16::from_str_radix(char.encode_utf8(&mut tmp), 16).is_ok()).then_some(value)                .ok_or_else(|| "Not a valid Git SHA".to_owned())        })        .and_then(|sha| {           std::process::Command::new("git")               .args([                   "rev-parse",                   "--quiet",                   "--verify",                   &format!("{sha}^{{commit}}")               ])               .output()               .map_err(|_| "Failed to spawn Git command to verify SHA".to_owned())               .and_then(|output|                   output                       .status.success()                       .then_some(sha)                       .ok_or_else(|| format!("SHA {sha} is not a valid Git SHA within this repository!")))        }).map(|sha| GitSha(sha.to_owned()))} #[derive(Parser)]pub(crate) struct GenerateWorkflowArgs {    #[arg(value_parser = parse_ref)]    /// The Git SHA to use when invoking this    pub(crate) sha: Option<GitSha>,} enum WorkflowSource {    Contextless(fn() -> Workflow),    WithContext(fn(&GenerateWorkflowArgs) -> Workflow),} struct WorkflowFile {    source: WorkflowSource,    r#type: WorkflowType,} impl WorkflowFile {    fn zed(f: fn() -> Workflow) -> WorkflowFile {        WorkflowFile {            source: WorkflowSource::Contextless(f),            r#type: WorkflowType::Zed,        }    }     fn extension(f: fn(&GenerateWorkflowArgs) -> Workflow) -> WorkflowFile {        WorkflowFile {            source: WorkflowSource::WithContext(f),            r#type: WorkflowType::ExtensionCi,        }    }     fn extension_shared(f: fn(&GenerateWorkflowArgs) -> Workflow) -> WorkflowFile {        WorkflowFile {            source: WorkflowSource::WithContext(f),            r#type: WorkflowType::ExtensionsShared,        }    }     fn generate_file(&self, workflow_args: &GenerateWorkflowArgs) -> Result<()> {        let workflow = match &self.source {            WorkflowSource::Contextless(f) => f(),            WorkflowSource::WithContext(f) => f(workflow_args),        };        let workflow_folder = self.r#type.folder_path();         fs::create_dir_all(&workflow_folder).with_context(|| {            format!("Failed to create directory: {}", workflow_folder.display())        })?;         let workflow_name = workflow            .name            .as_ref()            .expect("Workflow must have a name at this point");        let filename = format!(            "{}.yml",            workflow_name.rsplit("::").next().unwrap_or(workflow_name)        );         let workflow_path = workflow_folder.join(filename);         let content = workflow            .to_string()            .map_err(|e| anyhow::anyhow!("{:?}: {:?}", workflow_path, e))?;         let disclaimer = self.r#type.disclaimer(workflow_name);         let content = [disclaimer, content].join("\n");        fs::write(&workflow_path, content).map_err(Into::into)    }} #[derive(PartialEq, Eq, strum::EnumIter)]pub enum WorkflowType {    /// Workflows living in the Zed repository    Zed,    /// Workflows living in the `zed-extensions/workflows` repository that are    /// required workflows for PRs to the extension organization    ExtensionCi,    /// Workflows living in each of the extensions to perform checks and version    /// bumps until a better, more centralized system for that is in place.    ExtensionsShared,} impl WorkflowType {    fn disclaimer(&self, workflow_name: &str) -> String {        format!(            concat!(                "# Generated from xtask::workflows::{}{}\n",                "# Rebuild with `cargo xtask workflows`.",            ),            workflow_name,            (*self != WorkflowType::Zed)                .then_some(" within the Zed repository.")                .unwrap_or_default(),        )    }     pub fn folder_path(&self) -> PathBuf {        match self {            WorkflowType::Zed => PathBuf::from(".github/workflows"),            WorkflowType::ExtensionCi => PathBuf::from("extensions/workflows"),            WorkflowType::ExtensionsShared => PathBuf::from("extensions/workflows/shared"),        }    }} pub fn run_workflows(args: GenerateWorkflowArgs) -> Result<()> {    if !Path::new("crates/zed/").is_dir() {        anyhow::bail!("xtask workflows must be ran from the project root");    }     let workflows = [        WorkflowFile::zed(after_release::after_release),        WorkflowFile::zed(autofix_pr::autofix_pr),        WorkflowFile::zed(bump_patch_version::bump_patch_version),        WorkflowFile::zed(cherry_pick::cherry_pick),        WorkflowFile::zed(compare_perf::compare_perf),        WorkflowFile::zed(danger::danger),        WorkflowFile::zed(deploy_collab::deploy_collab),        WorkflowFile::zed(extension_bump::extension_bump),        WorkflowFile::zed(extension_auto_bump::extension_auto_bump),        WorkflowFile::zed(extension_tests::extension_tests),        WorkflowFile::zed(extension_workflow_rollout::extension_workflow_rollout),        WorkflowFile::zed(publish_extension_cli::publish_extension_cli),        WorkflowFile::zed(release::release),        WorkflowFile::zed(release_nightly::release_nightly),        WorkflowFile::zed(run_agent_evals::run_agent_evals),        WorkflowFile::zed(run_agent_evals::run_cron_unit_evals),        WorkflowFile::zed(run_agent_evals::run_unit_evals),        WorkflowFile::zed(run_bundling::run_bundling),        WorkflowFile::zed(run_tests::run_tests),        /* workflows used for CI/CD in extension repositories */        WorkflowFile::extension(extensions::run_tests::run_tests),        WorkflowFile::extension_shared(extensions::bump_version::bump_version),    ];     for workflow_file in workflows {        workflow_file.generate_file(&args)?;    }     workflow_checks::validate(Default::default())}