use gh_workflow::*; use crate::tasks::workflows::{ runners, steps::{ self, CommonPermissionSets, DownloadArtifactStep, FluentBuilder, IfNoFilesFound, NamedJob, RepositoryTarget, TokenPermissions, UploadArtifactStep, ZippyGitIdentity, named, use_clang, }, vars::{self, StepOutput, WorkflowInput}, }; pub fn autofix_pr() -> Workflow { let pr_number = WorkflowInput::string("pr_number", None); let run_clippy = WorkflowInput::bool("run_clippy", Some(true)); let run_autofix = run_autofix(&pr_number, &run_clippy); let commit_changes = commit_changes(&pr_number, &run_autofix); named::workflow() .with_minimal_permissions() .run_name(format!("autofix PR #{pr_number}")) .on(Event::default().workflow_dispatch( WorkflowDispatch::default() .add_input(pr_number.name, pr_number.input()) .add_input(run_clippy.name, run_clippy.input()), )) .concurrency( Concurrency::new(Expression::new(format!( "${{{{ github.workflow }}}}-{pr_number}" ))) .cancel_in_progress(true), ) .add_job(run_autofix.name.clone(), run_autofix.job) .add_job(commit_changes.name, commit_changes.job) } const PATCH_ARTIFACT_NAME: &str = "autofix-patch"; const PATCH_FILE_PATH: &str = "autofix.patch"; fn upload_patch_artifact() -> UploadArtifactStep { steps::upload_artifact(PATCH_ARTIFACT_NAME, PATCH_FILE_PATH) .if_no_files_found(IfNoFilesFound::Ignore) .retention_days(1) } fn download_patch_artifact() -> DownloadArtifactStep { steps::download_artifact().artifact_name(PATCH_ARTIFACT_NAME) } fn run_autofix(pr_number: &WorkflowInput, run_clippy: &WorkflowInput) -> NamedJob { fn checkout_pr(pr_number: &WorkflowInput) -> Step { named::bash(r#"gh pr checkout "$PR_NUMBER""#) .add_env(("PR_NUMBER", pr_number.to_string())) .add_env(("GITHUB_TOKEN", vars::GITHUB_TOKEN)) } fn install_cargo_machete() -> Step { steps::taiki_install_action("cargo-machete@0.7.0") } fn run_cargo_fmt() -> Step { named::bash("cargo fmt --all") } fn run_cargo_fix() -> Step { named::bash("cargo fix --workspace --allow-dirty --allow-staged") } fn run_cargo_machete_fix() -> Step { named::bash("cargo machete --fix") } fn run_clippy_fix() -> Step { named::bash("cargo clippy --workspace --fix --allow-dirty --allow-staged") } fn run_prettier_fix() -> Step { named::bash("./script/prettier --write") } fn create_patch() -> Step { named::bash(indoc::indoc! {r#" if git diff --quiet; then echo "No changes to commit" echo "has_changes=false" >> "$GITHUB_OUTPUT" else git diff > autofix.patch echo "has_changes=true" >> "$GITHUB_OUTPUT" fi "#}) .id("create-patch") } named::job(use_clang( Job::default() .runs_on(runners::LINUX_DEFAULT) .permissions( Permissions::default() .contents(Level::Read) .pull_requests(Level::Read), ) .outputs([( "has_changes".to_owned(), "${{ steps.create-patch.outputs.has_changes }}".to_owned(), )]) .add_step(steps::checkout_repo()) .add_step(checkout_pr(pr_number)) .add_step(steps::setup_cargo_config(runners::Platform::Linux)) .add_step(steps::cache_rust_dependencies_namespace()) .map(steps::install_linux_dependencies) .add_step(steps::setup_pnpm()) .add_step(install_cargo_machete().if_condition(Expression::new(run_clippy.to_string()))) .add_step(run_cargo_fix().if_condition(Expression::new(run_clippy.to_string()))) .add_step(run_cargo_machete_fix().if_condition(Expression::new(run_clippy.to_string()))) .add_step(run_clippy_fix().if_condition(Expression::new(run_clippy.to_string()))) .add_step(run_prettier_fix()) .add_step(run_cargo_fmt()) .add_step(create_patch()) .add_step(upload_patch_artifact()) .add_step(steps::cleanup_cargo_config(runners::Platform::Linux)), )) } fn commit_changes(pr_number: &WorkflowInput, autofix_job: &NamedJob) -> NamedJob { fn checkout_pr(pr_number: &WorkflowInput, token: &StepOutput) -> Step { named::bash(r#"gh pr checkout "$PR_NUMBER""#) .add_env(("PR_NUMBER", pr_number.to_string())) .add_env(("GITHUB_TOKEN", token)) } fn apply_patch() -> Step { named::bash("git apply autofix.patch") } fn commit_and_push(token: &StepOutput) -> Step { named::bash(indoc::indoc! {r#" git commit -am "Autofix" git push "#}) .with_zippy_git_identity() .add_env(("GITHUB_TOKEN", token)) } let (authenticate, token) = steps::authenticate_as_zippy() .for_repository(RepositoryTarget::current()) .with_permissions([ (TokenPermissions::Contents, Level::Write), (TokenPermissions::Workflows, Level::Write), ]) .into(); named::job( Job::default() .runs_on(runners::LINUX_SMALL) .needs(vec![autofix_job.name.clone()]) .cond(Expression::new(format!( "needs.{}.outputs.has_changes == 'true'", autofix_job.name ))) .add_step(authenticate) .add_step(steps::checkout_repo().with_token(&token)) .add_step(checkout_pr(pr_number, &token)) .add_step(download_patch_artifact()) .add_step(apply_patch()) .add_step(commit_and_push(&token)), ) }