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}