1use gh_workflow::*;
2use indoc::indoc;
3
4use crate::tasks::workflows::{
5 extension_bump::compare_versions,
6 run_tests::{fetch_ts_query_ls, orchestrate_for_extension, run_ts_query_ls, tests_pass},
7 runners,
8 steps::{
9 self, BASH_SHELL, CommonJobConditions, FluentBuilder, NamedJob,
10 cache_rust_dependencies_namespace, named,
11 },
12 vars::{PathCondition, StepOutput, WorkflowInput, one_workflow_per_non_main_branch},
13};
14
15pub(crate) const ZED_EXTENSION_CLI_SHA: &str = "03d8e9aee95ea6117d75a48bcac2e19241f6e667";
16
17// This should follow the set target in crates/extension/src/extension_builder.rs
18const EXTENSION_RUST_TARGET: &str = "wasm32-wasip2";
19
20// This is used by various extensions repos in the zed-extensions org to run automated tests.
21pub(crate) fn extension_tests() -> Workflow {
22 let should_check_rust = PathCondition::new("check_rust", r"^(Cargo.lock|Cargo.toml|.*\.rs)$");
23 let should_check_extension =
24 PathCondition::new("check_extension", r"^(extension\.toml|.*\.scm)$");
25
26 let orchestrate = with_extension_defaults(orchestrate_for_extension(&[
27 &should_check_rust,
28 &should_check_extension,
29 ]));
30
31 let jobs = [
32 orchestrate,
33 should_check_rust.guard(check_rust()),
34 should_check_extension.guard(check_extension()),
35 ];
36
37 let tests_pass = with_extension_defaults(tests_pass(&jobs));
38
39 let working_directory = WorkflowInput::string("working-directory", Some(".".to_owned()));
40
41 named::workflow()
42 .add_event(
43 Event::default().workflow_call(
44 WorkflowCall::default()
45 .add_input(working_directory.name, working_directory.call_input()),
46 ),
47 )
48 .concurrency(one_workflow_per_non_main_branch())
49 .add_env(("CARGO_TERM_COLOR", "always"))
50 .add_env(("RUST_BACKTRACE", 1))
51 .add_env(("CARGO_INCREMENTAL", 0))
52 .add_env(("ZED_EXTENSION_CLI_SHA", ZED_EXTENSION_CLI_SHA))
53 .add_env(("RUSTUP_TOOLCHAIN", "stable"))
54 .add_env(("CARGO_BUILD_TARGET", EXTENSION_RUST_TARGET))
55 .map(|workflow| {
56 jobs.into_iter()
57 .chain([tests_pass])
58 .fold(workflow, |workflow, job| {
59 workflow.add_job(job.name, job.job)
60 })
61 })
62}
63
64fn install_rust_target() -> Step<Run> {
65 named::bash(format!("rustup target add {EXTENSION_RUST_TARGET}",))
66}
67
68fn get_package_name() -> (Step<Run>, StepOutput) {
69 let step = named::bash(indoc! {r#"
70 PACKAGE_NAME="$(sed -n 's/^name = "\(.*\)"/\1/p' < Cargo.toml | head -1 | tr -d '[:space:]')"
71 echo "package_name=${PACKAGE_NAME}" >> "$GITHUB_OUTPUT"
72 "#})
73 .id("get-package-name");
74
75 let output = StepOutput::new(&step, "package_name");
76 (step, output)
77}
78
79fn cargo_fmt_package(package_name: &StepOutput) -> Step<Run> {
80 named::bash(r#"cargo fmt -p "$PACKAGE_NAME" -- --check"#)
81 .add_env(("PACKAGE_NAME", package_name.to_string()))
82}
83
84fn run_clippy(package_name: &StepOutput) -> Step<Run> {
85 named::bash(r#"cargo clippy -p "$PACKAGE_NAME" --release --all-features -- --deny warnings"#)
86 .add_env(("PACKAGE_NAME", package_name.to_string()))
87}
88
89fn run_nextest(package_name: &StepOutput) -> Step<Run> {
90 named::bash(
91 r#"cargo nextest run -p "$PACKAGE_NAME" --no-fail-fast --no-tests=warn --target "$(rustc -vV | sed -n 's|host: ||p')""#,
92 )
93 .add_env(("PACKAGE_NAME", package_name.to_string()))
94 .add_env(("NEXTEST_NO_TESTS", "warn"))
95}
96
97fn extension_job_defaults() -> Defaults {
98 Defaults::default().run(
99 RunDefaults::default()
100 .shell(BASH_SHELL)
101 .working_directory("${{ inputs.working-directory }}"),
102 )
103}
104
105fn with_extension_defaults(named_job: NamedJob) -> NamedJob {
106 NamedJob {
107 name: named_job.name,
108 job: named_job.job.defaults(extension_job_defaults()),
109 }
110}
111
112fn check_rust() -> NamedJob {
113 let (get_package, package_name) = get_package_name();
114
115 let job = Job::default()
116 .defaults(extension_job_defaults())
117 .with_repository_owner_guard()
118 .runs_on(runners::LINUX_LARGE_RAM)
119 .timeout_minutes(6u32)
120 .add_step(steps::checkout_repo())
121 .add_step(steps::cache_rust_dependencies_namespace())
122 .add_step(install_rust_target())
123 .add_step(get_package)
124 .add_step(cargo_fmt_package(&package_name))
125 .add_step(run_clippy(&package_name))
126 .add_step(steps::cargo_install_nextest())
127 .add_step(run_nextest(&package_name));
128
129 named::job(job)
130}
131
132pub(crate) fn check_extension() -> NamedJob {
133 let (cache_download, cache_hit) = cache_zed_extension_cli();
134 let (check_version_job, version_changed, _) = compare_versions();
135
136 let job = Job::default()
137 .defaults(extension_job_defaults())
138 .with_repository_owner_guard()
139 .runs_on(runners::LINUX_LARGE_RAM)
140 .timeout_minutes(6u32)
141 .add_step(steps::checkout_repo().with_full_history())
142 .add_step(cache_download)
143 .add_step(download_zed_extension_cli(cache_hit))
144 .add_step(cache_rust_dependencies_namespace()) // Extensions can compile Rust, so provide the cache if needed.
145 .add_step(check())
146 .add_step(fetch_ts_query_ls())
147 .add_step(run_ts_query_ls())
148 .add_step(check_version_job)
149 .add_step(verify_version_did_not_change(version_changed));
150
151 named::job(job)
152}
153
154pub fn cache_zed_extension_cli() -> (Step<Use>, StepOutput) {
155 let step = named::uses(
156 "actions",
157 "cache",
158 "0057852bfaa89a56745cba8c7296529d2fc39830",
159 )
160 .id("cache-zed-extension-cli")
161 .with(
162 Input::default()
163 .add("path", "zed-extension")
164 .add("key", "zed-extension-${{ env.ZED_EXTENSION_CLI_SHA }}"),
165 );
166 let output = StepOutput::new(&step, "cache-hit");
167 (step, output)
168}
169
170pub fn download_zed_extension_cli(cache_hit: StepOutput) -> Step<Run> {
171 named::bash(
172 indoc! {
173 r#"
174 wget --quiet "https://zed-extension-cli.nyc3.digitaloceanspaces.com/$ZED_EXTENSION_CLI_SHA/x86_64-unknown-linux-gnu/zed-extension" -O "$GITHUB_WORKSPACE/zed-extension"
175 chmod +x "$GITHUB_WORKSPACE/zed-extension"
176 "#,
177 }
178 ).if_condition(Expression::new(format!("{} != 'true'", cache_hit.expr())))
179}
180
181pub fn check() -> Step<Run> {
182 named::bash(indoc! {
183 r#"
184 mkdir -p /tmp/ext-scratch
185 mkdir -p /tmp/ext-output
186 "$GITHUB_WORKSPACE/zed-extension" --source-dir . --scratch-dir /tmp/ext-scratch --output-dir /tmp/ext-output
187 "#
188 })
189}
190
191fn verify_version_did_not_change(version_changed: StepOutput) -> Step<Run> {
192 named::bash(indoc! {r#"
193 if [[ "$VERSION_CHANGED" == "true" && "$GITHUB_EVENT_NAME" == "pull_request" && "$PR_USER_LOGIN" != "zed-zippy[bot]" ]] ; then
194 echo "Version change detected in your change!"
195 echo "Version changes happen in separate PRs and will be performed by the zed-zippy bot"
196 exit 42
197 fi
198 "#
199 })
200 .add_env(("VERSION_CHANGED", version_changed.to_string()))
201 .add_env(("PR_USER_LOGIN", "${{ github.event.pull_request.user.login }}"))
202}