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