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