From 3983f4dc83696501cc35bdf68bb3c3c5cc5f413a Mon Sep 17 00:00:00 2001 From: GabrielBBaldez <130607246+GabrielBBaldez@users.noreply.github.com> Date: Sun, 2 Aug 2026 16:33:38 -0300 Subject: [PATCH 1/2] ci: build, test and Plugin Verifier on every push and PR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The repo had no CI. Contributor PRs were landing with nothing checking that they compiled, let alone that the plugin still loaded on the IDE range the manifest claims. Two jobs: build — runs :core:test (the parser suite; :plugin has no tests by design, so `gradlew test` alone is green either way), then build, the two configuration checks, and buildPlugin, uploading the zip. verify — runs the IntelliJ Plugin Verifier against 2024.2.5 and 2024.3.5. The first is the oldest release in the 242 line sinceBuild claims, which is where an API the plugin uses might not exist yet; the compile target cannot surface that. untilBuild = 261.* stays unverified: no release exists to check against. Two traps worth recording, both of which produce a green run that verified nothing: - pluginVerification.ides accepts ide(IntelliJPlatformType, String) and then schedules no verification at all. Only the ide("IC-2024.2.5") string form works. The task still succeeds, having checked one IDE instead of two. - verifyPlugin needs pluginVerifier() in the dependencies block; without it the task fails outright, which is the friendlier of the two failures. The verify job therefore asserts that the number of report directories matches the number of declared IDEs, so a silently-dropped entry fails the build instead of passing quietly. --- .github/workflows/ci.yml | 104 +++++++++++++++++++++++++++++++++++++++ plugin/build.gradle.kts | 16 ++++++ 2 files changed, 120 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ff298c5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,104 @@ +name: CI +on: + push: + branches: [main] + pull_request: + +# A second push to a PR makes the first run's answer worthless — the verifier job downloads +# IDEs, so letting both finish is minutes of runner time spent on a stale commit. +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + name: Build and test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 21 + - uses: gradle/actions/setup-gradle@v4 + + # :core holds the parser and its tests; :plugin has none by design (its test task is + # disabled in build.gradle.kts), so `gradlew test` alone reports success either way. + # Naming :core:test means a removed test suite shows up as a task that no longer exists. + - run: ./gradlew :core:test + + - name: Test report + if: always() + run: | + set -euo pipefail + shopt -s nullglob + files=(core/build/test-results/test/TEST-*.xml) + if [ ${#files[@]} -eq 0 ]; then + echo "::error::no test results — :core:test produced nothing" + exit 1 + fi + awk -F'"' '/> "$GITHUB_STEP_SUMMARY" + + - run: ./gradlew build verifyPluginProjectConfiguration verifyPluginStructure + + - name: Build the distributable + run: ./gradlew buildPlugin + + - uses: actions/upload-artifact@v4 + with: + name: plugin-zip + path: plugin/build/distributions/*.zip + if-no-files-found: error + + verify: + name: Plugin Verifier + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 21 + - uses: gradle/actions/setup-gradle@v4 + + # Each IDE under test is a ~1GB download. Caching them separately from the Gradle cache + # keeps a dependency change from evicting them. + - uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches/modules-2/files-2.1/com.jetbrains.intellij.idea + ~/.pluginVerifier + key: ide-verifier-${{ hashFiles('plugin/build.gradle.kts') }} + restore-keys: ide-verifier- + + - run: ./gradlew verifyPlugin + + # The task passes when an IDE reports problems below the failure level, and it passes + # just as quietly when the ides {} block resolves to nothing. Assert on the reports: + # one directory per verified IDE, and the count has to match what is declared. + - name: Every declared IDE was actually verified + run: | + set -euo pipefail + shopt -s nullglob + reports=plugin/build/reports/pluginVerifier + declared=$(grep -c '^ *ide("' plugin/build.gradle.kts) + dirs=("$reports"/*/) + echo "declared=$declared verified=${#dirs[@]}" + if [ "${#dirs[@]}" -ne "$declared" ]; then + echo "::error::$declared IDEs declared in pluginVerification, ${#dirs[@]} verified" + for d in "${dirs[@]}"; do echo " $(basename "$d")"; done + exit 1 + fi + for d in "${dirs[@]}"; do + echo "- verified against $(basename "$d")" >> "$GITHUB_STEP_SUMMARY" + done + + - uses: actions/upload-artifact@v4 + if: always() + with: + name: plugin-verifier-reports + path: plugin/build/reports/pluginVerifier + if-no-files-found: warn diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index 79750d8..26b8923 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -21,6 +21,8 @@ dependencies { // installed IDE (no ~1GB SDK download); the default downloads Community for CI/others. val localIde = providers.gradleProperty("localIdePath").orNull if (localIde != null) local(localIde) else intellijIdeaCommunity("2024.3.5") + + pluginVerifier() // the CLI the verifyPlugin task shells out to } } @@ -35,6 +37,20 @@ intellijPlatform { untilBuild = "261.*" } } + + // sinceBuild above is a claim; this is what makes it true. 2024.2.5 is the oldest release + // in the 242 line, so it is where an API the plugin uses might not exist yet — the failure + // the compile target cannot show. untilBuild stays unverified on purpose: 261 has no + // release to check against, so it remains a forward-looking promise. + // + // Use the string notation. The ide(IntelliJPlatformType, String) overload accepts these + // versions and then schedules nothing, leaving a green run that verified one IDE. + pluginVerification { + ides { + ide("IC-2024.2.5") // the floor sinceBuild = 242 claims + ide("IC-2024.3.5") // what the plugin compiles against + } + } } // No test sources live here (the parser is tested in :core). Disable the platform test From 619bd5b09e270f31c883f99db0ac8818d8d1e70b Mon Sep 17 00:00:00 2001 From: GabrielBBaldez <130607246+GabrielBBaldez@users.noreply.github.com> Date: Sun, 2 Aug 2026 16:35:39 -0300 Subject: [PATCH 2/2] ci: mark gradlew executable, and stop the test guard blaming the wrong step MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first run failed both jobs with exit 126 — gradlew was committed 100644, so Linux runners could not execute it. Nobody noticed because the repo had no CI and every build until now was on Windows, where the mode bit is not consulted. The report step then said "no test results — :core:test produced nothing", which is true and misleading: the test step had already failed. It now only treats missing results as a finding when the test step reported success, which is the case it exists for — a suite that quietly stops running. --- .github/workflows/ci.yml | 11 +++++++++-- gradlew | 0 2 files changed, 9 insertions(+), 2 deletions(-) mode change 100644 => 100755 gradlew diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ff298c5..3511138 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,8 @@ jobs: # :core holds the parser and its tests; :plugin has none by design (its test task is # disabled in build.gradle.kts), so `gradlew test` alone reports success either way. # Naming :core:test means a removed test suite shows up as a task that no longer exists. - - run: ./gradlew :core:test + - id: coretest + run: ./gradlew :core:test - name: Test report if: always() @@ -34,7 +35,13 @@ jobs: shopt -s nullglob files=(core/build/test-results/test/TEST-*.xml) if [ ${#files[@]} -eq 0 ]; then - echo "::error::no test results — :core:test produced nothing" + # No results after a *failed* test step says nothing new — the job is already red + # for the real reason. It is only a finding when the step claimed success. + if [ "${{ steps.coretest.outcome }}" != "success" ]; then + echo "::notice::no test results, but the test step failed above — see it for the cause" + exit 0 + fi + echo "::error:::core:test succeeded and produced no results — did the suite disappear?" exit 1 fi awk -F'"' '/