1use std::cell::RefCell;
2
3use gh_workflow::{
4 Concurrency, Env, Expression, Step, WorkflowCallInput, WorkflowCallSecret,
5 WorkflowDispatchInput,
6};
7
8use crate::tasks::workflows::{runners::Platform, steps::NamedJob};
9
10macro_rules! secret {
11 ($secret_name:ident) => {
12 pub const $secret_name: &str = concat!("${{ secrets.", stringify!($secret_name), " }}");
13 };
14}
15
16macro_rules! var {
17 ($var_name:ident) => {
18 pub const $var_name: &str = concat!("${{ vars.", stringify!($var_name), " }}");
19 };
20}
21
22secret!(ANTHROPIC_API_KEY);
23secret!(OPENAI_API_KEY);
24secret!(GOOGLE_AI_API_KEY);
25secret!(GOOGLE_CLOUD_PROJECT);
26secret!(APPLE_NOTARIZATION_ISSUER_ID);
27secret!(APPLE_NOTARIZATION_KEY);
28secret!(APPLE_NOTARIZATION_KEY_ID);
29secret!(AZURE_SIGNING_CLIENT_ID);
30secret!(AZURE_SIGNING_CLIENT_SECRET);
31secret!(AZURE_SIGNING_TENANT_ID);
32secret!(CACHIX_AUTH_TOKEN);
33secret!(DIGITALOCEAN_SPACES_ACCESS_KEY);
34secret!(DIGITALOCEAN_SPACES_SECRET_KEY);
35secret!(GITHUB_TOKEN);
36secret!(MACOS_CERTIFICATE);
37secret!(MACOS_CERTIFICATE_PASSWORD);
38secret!(SENTRY_AUTH_TOKEN);
39secret!(ZED_CLIENT_CHECKSUM_SEED);
40secret!(ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON);
41secret!(ZED_SENTRY_MINIDUMP_ENDPOINT);
42secret!(SLACK_APP_ZED_UNIT_EVALS_BOT_TOKEN);
43secret!(ZED_ZIPPY_APP_ID);
44secret!(ZED_ZIPPY_APP_PRIVATE_KEY);
45secret!(DISCORD_WEBHOOK_RELEASE_NOTES);
46secret!(WINGET_TOKEN);
47secret!(VERCEL_TOKEN);
48secret!(SLACK_WEBHOOK_WORKFLOW_FAILURES);
49secret!(R2_ACCOUNT_ID);
50secret!(R2_ACCESS_KEY_ID);
51secret!(R2_SECRET_ACCESS_KEY);
52
53// todo(ci) make these secrets too...
54var!(AZURE_SIGNING_ACCOUNT_NAME);
55var!(AZURE_SIGNING_CERT_PROFILE_NAME);
56var!(AZURE_SIGNING_ENDPOINT);
57
58pub fn bundle_envs(platform: Platform) -> Env {
59 let env = Env::default()
60 .add("CARGO_INCREMENTAL", 0)
61 .add("ZED_CLIENT_CHECKSUM_SEED", ZED_CLIENT_CHECKSUM_SEED)
62 .add("ZED_MINIDUMP_ENDPOINT", ZED_SENTRY_MINIDUMP_ENDPOINT);
63
64 match platform {
65 Platform::Linux => env,
66 Platform::Mac => env
67 .add("MACOS_CERTIFICATE", MACOS_CERTIFICATE)
68 .add("MACOS_CERTIFICATE_PASSWORD", MACOS_CERTIFICATE_PASSWORD)
69 .add("APPLE_NOTARIZATION_KEY", APPLE_NOTARIZATION_KEY)
70 .add("APPLE_NOTARIZATION_KEY_ID", APPLE_NOTARIZATION_KEY_ID)
71 .add("APPLE_NOTARIZATION_ISSUER_ID", APPLE_NOTARIZATION_ISSUER_ID),
72 Platform::Windows => env
73 .add("AZURE_TENANT_ID", AZURE_SIGNING_TENANT_ID)
74 .add("AZURE_CLIENT_ID", AZURE_SIGNING_CLIENT_ID)
75 .add("AZURE_CLIENT_SECRET", AZURE_SIGNING_CLIENT_SECRET)
76 .add("ACCOUNT_NAME", AZURE_SIGNING_ACCOUNT_NAME)
77 .add("CERT_PROFILE_NAME", AZURE_SIGNING_CERT_PROFILE_NAME)
78 .add("ENDPOINT", AZURE_SIGNING_ENDPOINT)
79 .add("FILE_DIGEST", "SHA256")
80 .add("TIMESTAMP_DIGEST", "SHA256")
81 .add("TIMESTAMP_SERVER", "http://timestamp.acs.microsoft.com"),
82 }
83}
84
85pub fn one_workflow_per_non_main_branch() -> Concurrency {
86 one_workflow_per_non_main_branch_and_token("")
87}
88
89pub fn one_workflow_per_non_main_branch_and_token<T: AsRef<str>>(token: T) -> Concurrency {
90 Concurrency::default()
91 .group(format!(
92 concat!(
93 "${{{{ github.workflow }}}}-${{{{ github.ref_name }}}}-",
94 "${{{{ github.ref_name == 'main' && github.sha || 'anysha' }}}}{}"
95 ),
96 token.as_ref()
97 ))
98 .cancel_in_progress(true)
99}
100
101pub(crate) fn allow_concurrent_runs() -> Concurrency {
102 Concurrency::default()
103 .group("${{ github.workflow }}-${{ github.ref_name }}-${{ github.run_id }}")
104 .cancel_in_progress(true)
105}
106
107// Represents a pattern to check for changed files and corresponding output variable
108pub struct PathCondition {
109 pub name: &'static str,
110 pub pattern: &'static str,
111 pub invert: bool,
112 pub set_by_step: RefCell<Option<String>>,
113}
114impl PathCondition {
115 pub fn new(name: &'static str, pattern: &'static str) -> Self {
116 Self {
117 name,
118 pattern,
119 invert: false,
120 set_by_step: Default::default(),
121 }
122 }
123 pub fn inverted(name: &'static str, pattern: &'static str) -> Self {
124 Self {
125 name,
126 pattern,
127 invert: true,
128 set_by_step: Default::default(),
129 }
130 }
131 pub fn guard(&self, job: NamedJob) -> NamedJob {
132 let set_by_step = self
133 .set_by_step
134 .borrow()
135 .clone()
136 .unwrap_or_else(|| panic!("condition {},is never set", self.name));
137 NamedJob {
138 name: job.name,
139 job: job
140 .job
141 .add_need(set_by_step.clone())
142 .cond(Expression::new(format!(
143 "needs.{}.outputs.{} == 'true'",
144 &set_by_step, self.name
145 ))),
146 }
147 }
148}
149
150pub(crate) struct StepOutput {
151 pub name: &'static str,
152 step_id: String,
153}
154
155impl StepOutput {
156 pub fn new<T>(step: &Step<T>, name: &'static str) -> Self {
157 Self {
158 name,
159 step_id: step
160 .value
161 .id
162 .clone()
163 .expect("Steps that produce outputs must have an ID"),
164 }
165 }
166
167 pub fn expr(&self) -> String {
168 format!("steps.{}.outputs.{}", self.step_id, self.name)
169 }
170
171 pub fn as_job_output(self, job: &NamedJob) -> JobOutput {
172 JobOutput {
173 job_name: job.name.clone(),
174 name: self.name,
175 }
176 }
177}
178
179impl serde::Serialize for StepOutput {
180 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
181 where
182 S: serde::Serializer,
183 {
184 serializer.serialize_str(&self.to_string())
185 }
186}
187
188impl std::fmt::Display for StepOutput {
189 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
190 write!(f, "${{{{ {} }}}}", self.expr())
191 }
192}
193
194pub(crate) struct JobOutput {
195 job_name: String,
196 name: &'static str,
197}
198
199impl JobOutput {
200 pub fn expr(&self) -> String {
201 format!("needs.{}.outputs.{}", self.job_name, self.name)
202 }
203}
204
205impl serde::Serialize for JobOutput {
206 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
207 where
208 S: serde::Serializer,
209 {
210 serializer.serialize_str(&self.to_string())
211 }
212}
213
214impl std::fmt::Display for JobOutput {
215 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
216 write!(f, "${{{{ {} }}}}", self.expr())
217 }
218}
219
220pub struct WorkflowInput {
221 pub input_type: &'static str,
222 pub name: &'static str,
223 pub default: Option<String>,
224 pub description: Option<String>,
225}
226
227impl WorkflowInput {
228 pub fn string(name: &'static str, default: Option<String>) -> Self {
229 Self {
230 input_type: "string",
231 name,
232 default,
233 description: None,
234 }
235 }
236
237 pub fn bool(name: &'static str, default: Option<bool>) -> Self {
238 Self {
239 input_type: "boolean",
240 name,
241 default: default.as_ref().map(ToString::to_string),
242 description: None,
243 }
244 }
245
246 pub fn description(mut self, description: impl ToString) -> Self {
247 self.description = Some(description.to_string());
248 self
249 }
250
251 pub fn input(&self) -> WorkflowDispatchInput {
252 WorkflowDispatchInput {
253 description: self
254 .description
255 .clone()
256 .unwrap_or_else(|| self.name.to_owned()),
257 required: self.default.is_none(),
258 input_type: self.input_type.to_owned(),
259 default: self.default.clone(),
260 }
261 }
262
263 pub fn call_input(&self) -> WorkflowCallInput {
264 WorkflowCallInput {
265 description: self.name.to_owned(),
266 required: self.default.is_none(),
267 input_type: self.input_type.to_owned(),
268 default: self.default.clone(),
269 }
270 }
271
272 pub(crate) fn expr(&self) -> String {
273 format!("inputs.{}", self.name)
274 }
275}
276
277impl std::fmt::Display for WorkflowInput {
278 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
279 write!(f, "${{{{ {} }}}}", self.expr())
280 }
281}
282
283impl serde::Serialize for WorkflowInput {
284 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
285 where
286 S: serde::Serializer,
287 {
288 serializer.serialize_str(&self.to_string())
289 }
290}
291
292pub(crate) struct WorkflowSecret {
293 pub name: &'static str,
294 description: String,
295 required: bool,
296}
297
298impl WorkflowSecret {
299 pub fn new(name: &'static str, description: impl ToString) -> Self {
300 Self {
301 name,
302 description: description.to_string(),
303 required: true,
304 }
305 }
306
307 pub fn secret_configuration(&self) -> WorkflowCallSecret {
308 WorkflowCallSecret {
309 description: self.description.clone(),
310 required: self.required,
311 }
312 }
313}
314
315impl std::fmt::Display for WorkflowSecret {
316 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
317 write!(f, "${{{{ secrets.{} }}}}", self.name)
318 }
319}
320
321impl serde::Serialize for WorkflowSecret {
322 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
323 where
324 S: serde::Serializer,
325 {
326 serializer.serialize_str(&self.to_string())
327 }
328}
329
330pub mod assets {
331 // NOTE: these asset names also exist in the zed.dev codebase.
332 pub const MAC_AARCH64: &str = "Zed-aarch64.dmg";
333 pub const MAC_X86_64: &str = "Zed-x86_64.dmg";
334 pub const LINUX_AARCH64: &str = "zed-linux-aarch64.tar.gz";
335 pub const LINUX_X86_64: &str = "zed-linux-x86_64.tar.gz";
336 pub const WINDOWS_X86_64: &str = "Zed-x86_64.exe";
337 pub const WINDOWS_AARCH64: &str = "Zed-aarch64.exe";
338
339 pub const REMOTE_SERVER_MAC_AARCH64: &str = "zed-remote-server-macos-aarch64.gz";
340 pub const REMOTE_SERVER_MAC_X86_64: &str = "zed-remote-server-macos-x86_64.gz";
341 pub const REMOTE_SERVER_LINUX_AARCH64: &str = "zed-remote-server-linux-aarch64.gz";
342 pub const REMOTE_SERVER_LINUX_X86_64: &str = "zed-remote-server-linux-x86_64.gz";
343 pub const REMOTE_SERVER_WINDOWS_AARCH64: &str = "zed-remote-server-windows-aarch64.zip";
344 pub const REMOTE_SERVER_WINDOWS_X86_64: &str = "zed-remote-server-windows-x86_64.zip";
345
346 pub fn all() -> Vec<&'static str> {
347 vec![
348 MAC_AARCH64,
349 MAC_X86_64,
350 LINUX_AARCH64,
351 LINUX_X86_64,
352 WINDOWS_X86_64,
353 WINDOWS_AARCH64,
354 REMOTE_SERVER_MAC_AARCH64,
355 REMOTE_SERVER_MAC_X86_64,
356 REMOTE_SERVER_LINUX_AARCH64,
357 REMOTE_SERVER_LINUX_X86_64,
358 REMOTE_SERVER_WINDOWS_AARCH64,
359 REMOTE_SERVER_WINDOWS_X86_64,
360 ]
361 }
362}