release.rs

  1use gh_workflow::{Event, Expression, Push, Run, Step, Use, Workflow};
  2
  3use crate::tasks::workflows::{
  4    run_bundling::{bundle_linux, bundle_mac, bundle_windows},
  5    run_tests,
  6    runners::{self, Arch},
  7    steps::{self, FluentBuilder, NamedJob, dependant_job, named, release_job},
  8    vars::{self, assets},
  9};
 10
 11pub(crate) fn release() -> Workflow {
 12    let macos_tests = run_tests::run_platform_tests(runners::Platform::Mac);
 13    let linux_tests = run_tests::run_platform_tests(runners::Platform::Linux);
 14    let windows_tests = run_tests::run_platform_tests(runners::Platform::Windows);
 15    let check_scripts = run_tests::check_scripts();
 16
 17    let create_draft_release = create_draft_release();
 18
 19    let bundle = ReleaseBundleJobs {
 20        linux_aarch64: bundle_linux(Arch::AARCH64, None, &[&linux_tests, &check_scripts]),
 21        linux_x86_64: bundle_linux(Arch::X86_64, None, &[&linux_tests, &check_scripts]),
 22        mac_aarch64: bundle_mac(Arch::AARCH64, None, &[&macos_tests, &check_scripts]),
 23        mac_x86_64: bundle_mac(Arch::X86_64, None, &[&macos_tests, &check_scripts]),
 24        windows_aarch64: bundle_windows(Arch::AARCH64, None, &[&windows_tests, &check_scripts]),
 25        windows_x86_64: bundle_windows(Arch::X86_64, None, &[&windows_tests, &check_scripts]),
 26    };
 27
 28    let upload_release_assets = upload_release_assets(&[&create_draft_release], &bundle);
 29
 30    let auto_release_preview = auto_release_preview(&[&upload_release_assets]);
 31    let notify_on_failure = notify_on_failure(&[&upload_release_assets, &auto_release_preview]);
 32
 33    named::workflow()
 34        .on(Event::default().push(Push::default().tags(vec!["v*".to_string()])))
 35        .concurrency(vars::one_workflow_per_non_main_branch())
 36        .add_env(("CARGO_TERM_COLOR", "always"))
 37        .add_env(("RUST_BACKTRACE", "1"))
 38        .add_job(macos_tests.name, macos_tests.job)
 39        .add_job(linux_tests.name, linux_tests.job)
 40        .add_job(windows_tests.name, windows_tests.job)
 41        .add_job(check_scripts.name, check_scripts.job)
 42        .add_job(create_draft_release.name, create_draft_release.job)
 43        .map(|mut workflow| {
 44            for job in bundle.into_jobs() {
 45                workflow = workflow.add_job(job.name, job.job);
 46            }
 47            workflow
 48        })
 49        .add_job(upload_release_assets.name, upload_release_assets.job)
 50        .add_job(auto_release_preview.name, auto_release_preview.job)
 51        .add_job(notify_on_failure.name, notify_on_failure.job)
 52}
 53
 54pub(crate) struct ReleaseBundleJobs {
 55    pub linux_aarch64: NamedJob,
 56    pub linux_x86_64: NamedJob,
 57    pub mac_aarch64: NamedJob,
 58    pub mac_x86_64: NamedJob,
 59    pub windows_aarch64: NamedJob,
 60    pub windows_x86_64: NamedJob,
 61}
 62
 63impl ReleaseBundleJobs {
 64    pub fn jobs(&self) -> Vec<&NamedJob> {
 65        vec![
 66            &self.linux_aarch64,
 67            &self.linux_x86_64,
 68            &self.mac_aarch64,
 69            &self.mac_x86_64,
 70            &self.windows_aarch64,
 71            &self.windows_x86_64,
 72        ]
 73    }
 74
 75    pub fn into_jobs(self) -> Vec<NamedJob> {
 76        vec![
 77            self.linux_aarch64,
 78            self.linux_x86_64,
 79            self.mac_aarch64,
 80            self.mac_x86_64,
 81            self.windows_aarch64,
 82            self.windows_x86_64,
 83        ]
 84    }
 85}
 86
 87pub(crate) fn create_sentry_release() -> Step<Use> {
 88    named::uses(
 89        "getsentry",
 90        "action-release",
 91        "526942b68292201ac6bbb99b9a0747d4abee354c", // v3
 92    )
 93    .add_env(("SENTRY_ORG", "zed-dev"))
 94    .add_env(("SENTRY_PROJECT", "zed"))
 95    .add_env(("SENTRY_AUTH_TOKEN", vars::SENTRY_AUTH_TOKEN))
 96    .add_with(("environment", "production"))
 97}
 98
 99fn auto_release_preview(deps: &[&NamedJob; 1]) -> NamedJob {
100    named::job(
101        dependant_job(deps)
102            .runs_on(runners::LINUX_SMALL)
103            .cond(Expression::new(indoc::indoc!(
104                r#"startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '-pre') && !endsWith(github.ref, '.0-pre')"#
105            )))
106            .add_step(
107                steps::script(
108                    r#"gh release edit "$GITHUB_REF_NAME" --repo=zed-industries/zed --draft=false"#,
109                )
110                .add_env(("GITHUB_TOKEN", vars::GITHUB_TOKEN)),
111            )
112    )
113}
114
115pub(crate) fn download_workflow_artifacts() -> Step<Use> {
116    named::uses(
117        "actions",
118        "download-artifact",
119        "018cc2cf5baa6db3ef3c5f8a56943fffe632ef53", // v6.0.0
120    )
121    .add_with(("path", "./artifacts/"))
122}
123
124pub(crate) fn prep_release_artifacts() -> Step<Run> {
125    let mut script_lines = vec!["mkdir -p release-artifacts/\n".to_string()];
126    for asset in assets::all() {
127        let mv_command = format!("mv ./artifacts/{asset}/{asset} release-artifacts/{asset}");
128        script_lines.push(mv_command)
129    }
130
131    named::bash(&script_lines.join("\n"))
132}
133
134fn upload_release_assets(deps: &[&NamedJob], bundle: &ReleaseBundleJobs) -> NamedJob {
135    let mut deps = deps.to_vec();
136    deps.extend(bundle.jobs());
137
138    named::job(
139        dependant_job(&deps)
140            .runs_on(runners::LINUX_MEDIUM)
141            .add_step(download_workflow_artifacts())
142            .add_step(steps::script("ls -lR ./artifacts"))
143            .add_step(prep_release_artifacts())
144            .add_step(
145                steps::script("gh release upload \"$GITHUB_REF_NAME\" --repo=zed-industries/zed release-artifacts/*")
146                    .add_env(("GITHUB_TOKEN", vars::GITHUB_TOKEN)),
147            ),
148    )
149}
150
151fn create_draft_release() -> NamedJob {
152    fn generate_release_notes() -> Step<Run> {
153        named::bash(
154            r#"node --redirect-warnings=/dev/null ./script/draft-release-notes "$RELEASE_VERSION" "$RELEASE_CHANNEL" > target/release-notes.md"#,
155        )
156    }
157
158    fn create_release() -> Step<Run> {
159        named::bash("script/create-draft-release target/release-notes.md")
160            .add_env(("GITHUB_TOKEN", vars::GITHUB_TOKEN))
161    }
162
163    named::job(
164        release_job(&[])
165            .runs_on(runners::LINUX_SMALL)
166            // We need to fetch more than one commit so that `script/draft-release-notes`
167            // is able to diff between the current and previous tag.
168            //
169            // 25 was chosen arbitrarily.
170            .add_step(
171                steps::checkout_repo()
172                    .add_with(("fetch-depth", 25))
173                    .add_with(("clean", false))
174                    .add_with(("ref", "${{ github.ref }}")),
175            )
176            .add_step(steps::script("script/determine-release-channel"))
177            .add_step(steps::script("mkdir -p target/"))
178            .add_step(generate_release_notes())
179            .add_step(create_release()),
180    )
181}
182
183pub(crate) fn notify_on_failure(deps: &[&NamedJob]) -> NamedJob {
184    fn notify_slack() -> Step<Run> {
185        named::bash(
186            "curl -X POST -H 'Content-type: application/json'\\\n --data '{\"text\":\"${{ github.workflow }} failed:  ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"}' \"$SLACK_WEBHOOK\""
187        ).add_env(("SLACK_WEBHOOK", vars::SLACK_WEBHOOK_WORKFLOW_FAILURES))
188    }
189
190    let job = dependant_job(deps)
191        .runs_on(runners::LINUX_SMALL)
192        .cond(Expression::new("failure()"))
193        .add_step(notify_slack());
194    named::job(job)
195}