mirror of
https://github.com/Darkrock-Studios/hammer-editor.git
synced 2026-08-05 23:59:46 +00:00
Adds a static-analysis job to Build CI running two self-contained scanners as a gate, replacing reliance on out-of-band Codacy: - Semgrep against hand-written rules in .semgrep/ (raw SQL to prepareStatement), with --error; honors // nosemgrep. - gitleaks for committed secrets, pinned binary; fixtures allowlisted in .gitleaks.toml. Both tool versions are pinned for reproducible gating. Also fixes and extends the migrator's SQL suppression (correct placement, both rule ids), and suppresses the parity-check COUNT false positive. Supersedes #746.
344 lines
14 KiB
YAML
344 lines
14 KiB
YAML
name: Build CI
|
|
|
|
on:
|
|
push:
|
|
branches: [ "develop" ]
|
|
pull_request:
|
|
branches: [ "develop" ]
|
|
|
|
jobs:
|
|
build:
|
|
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
- name: set up JDK 21
|
|
uses: actions/setup-java@v5
|
|
with:
|
|
java-version: '21'
|
|
distribution: 'temurin'
|
|
cache: gradle
|
|
|
|
- name: Set up Node
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '22'
|
|
|
|
- name: Grant execute permission for gradlew
|
|
run: chmod +x gradlew
|
|
- name: Build with Gradle
|
|
run: ./gradlew buildDebug
|
|
# Compiles + packages the Android instrumented (androidTest) source set without a device.
|
|
# buildDebug does not touch androidTest, so stale instrumented tests rot silently otherwise.
|
|
- name: Compile Android instrumented tests
|
|
run: ./gradlew :android:assembleDebugAndroidTest
|
|
- name: Run Kover
|
|
run: ./gradlew koverXmlReport
|
|
- name: Upload coverage reports to Codecov
|
|
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
|
with:
|
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
# Explicit gate for the server unit tests (incl. EntityHashSensitivityTest). These otherwise
|
|
# only run as a side-effect of koverXmlReport, which is fragile - a kover config change would
|
|
# silently stop running them.
|
|
- name: Run Server Unit Tests
|
|
id: server-tests
|
|
run: ./gradlew :server:test
|
|
- name: Upload server test report on failure
|
|
if: failure() && steps.server-tests.outcome == 'failure'
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: server-tests-report
|
|
path: |
|
|
server/build/reports/tests/test/
|
|
server/build/test-results/test/
|
|
- name: Run JavaScript Unit Tests
|
|
run: ./gradlew :server:jsTest
|
|
- name: Run Server Migration Tests
|
|
run: ./gradlew :server:verifySqlDelightMigration
|
|
- name: Run Round-Trip Sync Integration Tests
|
|
id: integration-tests
|
|
run: ./gradlew :integrationTests:jvmTest
|
|
- name: Upload integration test report on failure
|
|
if: failure() && steps.integration-tests.outcome == 'failure'
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: integration-tests-report
|
|
path: |
|
|
integrationTests/build/reports/tests/jvmTest/
|
|
integrationTests/build/test-results/jvmTest/
|
|
|
|
static-analysis:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
- name: Set up Python
|
|
uses: actions/setup-python@v5
|
|
with:
|
|
python-version: '3.12'
|
|
- name: Install Semgrep
|
|
run: pip install semgrep==1.170.0
|
|
# Self-contained code-pattern rules under .semgrep/ (no registry fetch, no account).
|
|
# Honors `// nosemgrep` comments. `--error` makes an unsuppressed finding fail the build.
|
|
- name: Run Semgrep (custom rules)
|
|
run: semgrep scan --config .semgrep --error --metrics off
|
|
# Pinned for reproducibility — a tool auto-update must never spontaneously fail an
|
|
# unrelated PR's gate. Bump deliberately to pick up new secret-detection rules.
|
|
- name: Install gitleaks
|
|
env:
|
|
GITLEAKS_VERSION: 8.30.1
|
|
run: |
|
|
curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
|
|
| sudo tar -xz -C /usr/local/bin gitleaks
|
|
gitleaks version
|
|
# Fails the build on any committed secret. Known test fixtures are allowlisted in
|
|
# .gitleaks.toml; the JSON report is parsed so failures list rule + file:line.
|
|
- name: Run gitleaks
|
|
run: |
|
|
gitleaks dir . --config .gitleaks.toml --no-banner --redact \
|
|
--report-format json --report-path gitleaks-report.json --exit-code 2 || true
|
|
count=$(python3 -c "import json; print(len(json.load(open('gitleaks-report.json'))))")
|
|
if [ "$count" -gt 0 ]; then
|
|
echo "::error::gitleaks found $count secret(s):"
|
|
python3 -c "import json; [print(f\" {x['RuleID']} {x['File']}:{x['StartLine']}\") for x in json.load(open('gitleaks-report.json'))]"
|
|
exit 1
|
|
fi
|
|
echo "gitleaks: no leaks"
|
|
|
|
android-instrumented-tests:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
- name: set up JDK 21
|
|
uses: actions/setup-java@v5
|
|
with:
|
|
java-version: '21'
|
|
distribution: 'temurin'
|
|
cache: gradle
|
|
|
|
- name: Grant execute permission for gradlew
|
|
run: chmod +x gradlew
|
|
|
|
# The hardware-accelerated emulator needs KVM, which ubuntu-latest exposes once permissioned.
|
|
- name: Enable KVM
|
|
run: |
|
|
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
|
sudo udevadm control --reload-rules
|
|
sudo udevadm trigger --name-match=kvm
|
|
|
|
# Cache the emulator system image + AVD snapshot so subsequent runs skip the slow cold
|
|
# boot. Keyed on every parameter that defines the AVD - bump the key if any `with:` value
|
|
# below changes, or the cached snapshot won't match.
|
|
- name: AVD cache
|
|
uses: actions/cache@v6
|
|
id: avd-cache
|
|
with:
|
|
path: |
|
|
~/.android/avd/*
|
|
~/.android/adb*
|
|
key: avd-api34-x86_64-google_apis-pixel_6
|
|
|
|
# On a cache miss, cold-boot once and let the action save a snapshot for the cache. No tests here.
|
|
- name: Create AVD and generate snapshot for caching
|
|
if: steps.avd-cache.outputs.cache-hit != 'true'
|
|
uses: reactivecircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d # v2.38.0
|
|
with:
|
|
api-level: 34
|
|
arch: x86_64
|
|
target: google_apis
|
|
profile: pixel_6
|
|
force-avd-creation: false
|
|
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
|
|
script: echo "Generated AVD snapshot for caching."
|
|
|
|
- name: Run instrumented tests on emulator
|
|
id: instrumented-tests
|
|
uses: reactivecircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d # v2.38.0
|
|
with:
|
|
api-level: 34
|
|
arch: x86_64
|
|
target: google_apis
|
|
profile: pixel_6
|
|
force-avd-creation: false
|
|
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
|
|
script: ./gradlew :android:connectedDebugAndroidTest
|
|
|
|
- name: Upload instrumented test report on failure
|
|
if: failure() && steps.instrumented-tests.outcome == 'failure'
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: instrumented-tests-report
|
|
path: |
|
|
android/build/reports/androidTests/connected/
|
|
android/build/outputs/androidTest-results/connected/
|
|
|
|
# The ubuntu `build` job never touches the iOS targets, so Native-only breakage
|
|
# in commonMain slips through otherwise. This job both compiles the iOS targets
|
|
# and runs the Kotlin/Native unit tests on the simulator.
|
|
ios-compile:
|
|
name: iOS compile & test
|
|
runs-on: macos-latest
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
- name: Select Xcode 26
|
|
# macos-latest still defaults to Xcode 16.4 (iOS 18.5 SDK), but linking the
|
|
# iOS test executable pulls in Compose's ui-uikit, which references symbols
|
|
# (e.g. UIViewLayoutRegion) that only exist in the iOS 26 SDK. Without this
|
|
# the link fails non-deterministically depending on the runner image. Glob
|
|
# keeps us off a pinned patch version that could vanish on a future image.
|
|
run: |
|
|
XCODE_APP="$(ls -d /Applications/Xcode_26*.app | sort -V | tail -1)"
|
|
echo "Using $XCODE_APP"
|
|
sudo xcode-select -s "$XCODE_APP/Contents/Developer"
|
|
xcodebuild -version
|
|
- name: set up JDK 21
|
|
uses: actions/setup-java@v5
|
|
with:
|
|
java-version: '21'
|
|
distribution: 'temurin'
|
|
cache: gradle
|
|
- name: Grant execute permission for gradlew
|
|
run: chmod +x gradlew
|
|
- name: Compile iOS Kotlin targets
|
|
run: ./gradlew :composeUi:compileKotlinIosArm64 :composeUi:compileKotlinIosSimulatorArm64
|
|
- name: Run iOS unit tests
|
|
id: ios-tests
|
|
run: ./gradlew :common:iosSimulatorArm64Test
|
|
- name: Upload iOS test report on failure
|
|
if: failure() && steps.ios-tests.outcome == 'failure'
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: ios-tests-report
|
|
path: |
|
|
common/build/reports/tests/iosSimulatorArm64Test/
|
|
common/build/test-results/iosSimulatorArm64Test/
|
|
|
|
# Runs the real iOS app on a simulator and drives it through XCUITest (the iOS analogue of the
|
|
# android-instrumented-tests job). The whole UI is Compose Multiplatform, so the tests target
|
|
# Compose testTags, which the app exposes to the iOS accessibility tree when launched with
|
|
# --uitesting (see composeUi .../MainViewController.kt and ios/iosUITests).
|
|
ios-ui-tests:
|
|
name: iOS UI tests
|
|
runs-on: macos-latest
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
- name: Select Xcode 26
|
|
# Match ios-compile: linking Compose's ui-uikit needs the iOS 26 SDK.
|
|
run: |
|
|
XCODE_APP="$(ls -d /Applications/Xcode_26*.app | sort -V | tail -1)"
|
|
echo "Using $XCODE_APP"
|
|
sudo xcode-select -s "$XCODE_APP/Contents/Developer"
|
|
xcodebuild -version
|
|
- name: set up JDK 21
|
|
uses: actions/setup-java@v5
|
|
with:
|
|
java-version: '21'
|
|
distribution: 'temurin'
|
|
cache: gradle
|
|
- name: Grant execute permission for gradlew
|
|
run: chmod +x gradlew
|
|
# Pick the newest available iPhone simulator rather than pinning a device that a future
|
|
# Xcode image might drop, then boot it so the test run doesn't race first-boot.
|
|
- name: Select & boot a simulator
|
|
id: sim
|
|
run: |
|
|
UDID=$(xcrun simctl list devices available --json \
|
|
| jq -r '[.devices[][] | select(.name | startswith("iPhone"))] | last | .udid')
|
|
if [ -z "$UDID" ] || [ "$UDID" = "null" ]; then
|
|
echo "No iPhone simulator available"; xcrun simctl list devices available; exit 1
|
|
fi
|
|
echo "Using simulator $UDID"
|
|
# Disable the hardware keyboard before boot so the software keyboard shows — Compose
|
|
# text fields need it for XCUITest text entry (see ios/scripts).
|
|
bash ios/scripts/disable_sim_hardware_keyboard.sh
|
|
xcrun simctl boot "$UDID" || true
|
|
echo "udid=$UDID" >> "$GITHUB_OUTPUT"
|
|
- name: Run iOS UI tests
|
|
id: ios-ui-tests
|
|
run: |
|
|
set -o pipefail
|
|
xcodebuild test \
|
|
-project ios/ios.xcodeproj \
|
|
-scheme iosUITests \
|
|
-destination "id=${{ steps.sim.outputs.udid }}" \
|
|
-configuration Debug \
|
|
-resultBundlePath ios-ui-tests.xcresult \
|
|
CODE_SIGNING_ALLOWED=NO
|
|
- name: Upload iOS UI test results on failure
|
|
if: failure() && steps.ios-ui-tests.outcome == 'failure'
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: ios-ui-tests-report
|
|
path: ios-ui-tests.xcresult
|
|
|
|
# ios:
|
|
# runs-on: macos-latest
|
|
# steps:
|
|
# - name: Checkout
|
|
# uses: actions/checkout@v4
|
|
#
|
|
# - name: check Xcode version
|
|
# run: /usr/bin/xcodebuild -version
|
|
#
|
|
# - name: Get swift version
|
|
# run: swift --version
|
|
#
|
|
# - name: set up JDK 21
|
|
# uses: actions/setup-java@v4
|
|
# with:
|
|
# java-version: '21'
|
|
# distribution: 'temurin'
|
|
# cache: gradle
|
|
#
|
|
# - name: Grant execute permission for gradlew
|
|
# run: chmod +x gradlew
|
|
#
|
|
# - name: Install the Apple certificate and provisioning profile
|
|
# env:
|
|
# BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
|
|
# P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
|
|
# BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.BUILD_PROVISION_PROFILE_BASE64 }}
|
|
# KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
|
# run: |
|
|
# # create variables
|
|
# CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
|
|
# PP_PATH=$RUNNER_TEMP/build_pp.mobileprovision
|
|
# KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
|
|
#
|
|
# # import certificate and provisioning profile from secrets
|
|
# echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o "$CERTIFICATE_PATH"
|
|
# echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o "$PP_PATH"
|
|
#
|
|
# # create temporary keychain
|
|
# security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
|
# security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
|
|
# security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
|
#
|
|
# # import certificate to keychain
|
|
# security import "$CERTIFICATE_PATH" -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
|
|
# security list-keychain -d user -s "$KEYCHAIN_PATH"
|
|
#
|
|
# # apply provisioning profile
|
|
# mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
|
|
# cp "$PP_PATH" ~/Library/MobileDevice/Provisioning\ Profiles
|
|
#
|
|
# - name: Set Default Scheme
|
|
# run: |
|
|
# cd ios
|
|
# default="ios"
|
|
# echo $default | cat >default
|
|
# echo Using default scheme: $default
|
|
#
|
|
# - name: Build
|
|
# env:
|
|
# scheme: ${{ 'default' }}
|
|
# platform: ${{ 'iOS Simulator' }}
|
|
# run: |
|
|
# cd ios
|
|
# # xcrun xctrace returns via stderr, not the expected stdout (see https://developer.apple.com/forums/thread/663959)
|
|
# device=`xcrun xctrace list devices 2>&1 | grep -oE 'iPhone.*?[^\(]+' | head -1 | awk '{$1=$1;print}' | sed -e "s/ Simulator$//"`
|
|
# if [ $scheme = default ]; then scheme=$(cat default); fi
|
|
# if [ "`ls -A | grep -i \\.xcworkspace\$`" ]; then filetype_parameter="workspace" && file_to_build="`ls -A | grep -i \\.xcworkspace\$`"; else filetype_parameter="project" && file_to_build="`ls -A | grep -i \\.xcodeproj\$`"; fi
|
|
# file_to_build=`echo $file_to_build | awk '{$1=$1;print}'`
|
|
# xcodebuild build-for-testing -scheme "$scheme" -sdk iphoneos -allowProvisioningUpdates
|