diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..a2d10723 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +scripts/windows-run-gradle-capped.ps1 text eol=crlf +scripts/windows-run-slice.ps1 text eol=crlf diff --git a/.github/workflows/android-verify.yml b/.github/workflows/android-verify.yml new file mode 100644 index 00000000..b0359d99 --- /dev/null +++ b/.github/workflows/android-verify.yml @@ -0,0 +1,136 @@ +# Android embedding verify: assembles the android/ AAR subproject on the same +# self-hosted linux runner as dex-verify.yml (dsh-jvm-1). Zero local-gradle +# policy for the feature lanes — CI is the verifier, so this workflow is the +# gate that keeps android/ compiling against AGP + the pinned deps. +# Modeled on dex-verify.yml: workflow-level toolchain env, scoped daemon +# cleanup, non-fatal artifact uploads, and the chmod +x ./gradlew lesson. +name: android-verify + +on: + workflow_dispatch: + push: + branches: [windows-verify, linux-verify] + +# Machine-specific toolchain locations for the self-hosted Linux runner. +# Kept at workflow level so the machine owner edits ONE place here. +# AGP 8.5.x requires the Gradle daemon to run on JDK 17 — hence temurin-17, +# while the Kotlin toolchain (jvmTarget 11) is satisfied by the compile. +env: + JAVA_HOME: /home/dingyi/.jdks/temurin-17 + ANDROID_HOME: /home/dingyi/Android/Sdk + +jobs: + android-assemble: + runs-on: [self-hosted, linux, x64] + timeout-minutes: 30 + steps: + - name: Checkout + uses: actions/checkout@v4 + + # Convert the platform android.jar (Java bytecode stubs) to dex so the + # sample app can load it through DexClassLoader on-device — the reflection + # pipeline then works unchanged, with no desktop JDK required. + - name: Convert android.jar to android.dex + run: | + set -euxo pipefail + export PATH="$JAVA_HOME/bin:$PATH" + D8=$(ls "$ANDROID_HOME"/build-tools/*/d8 | sort | tail -1) + echo "d8: $D8" + mkdir -p android/sample/src/main/assets + "$D8" --release --min-api 26 --lib "$ANDROID_HOME/platforms/android-35/android.jar" \ + --output android/sample/src/main/assets \ + "$ANDROID_HOME/platforms/android-35/android.jar" + # d8 names its output classes.dex regardless of input name + mv android/sample/src/main/assets/classes.dex android/sample/src/main/assets/android.dex + ls -la android/sample/src/main/assets/android.dex + + - name: Show environment + run: | + export PATH="$JAVA_HOME/bin:$PATH" + echo "JAVA_HOME=$JAVA_HOME" + java -version + echo "ANDROID_HOME=$ANDROID_HOME" + git rev-parse --short HEAD + + # ANDROID_HOME is pre-set on the runner; sdkmanager may or may not be on + # PATH depending on the cmdline-tools layout — warn, never fail here. + # Missing platform/build-tools are auto-installed by AGP iff + # SDK licenses are already accepted on this machine. + - name: Verify Android SDK + run: | + echo "ANDROID_HOME=$ANDROID_HOME" + # The runner has no cmdline-tools (no sdkmanager), so AGP's own + # auto-installer is the only installer. It refuses to run unless the + # license files exist — write the standard license hashes directly + # (these are the well-known SHA1s of the SDK license texts, same on + # every machine; sdkmanager itself just writes these files). + mkdir -p "$ANDROID_HOME/licenses" + printf "24333f8a63b6825ea9c5514f83c2829b004d1fee\n" > "$ANDROID_HOME/licenses/android-sdk-license" + printf "84831b9409646a918e30573bab4c9c91346d8abd\n" > "$ANDROID_HOME/licenses/android-sdk-preview-license" + echo "license files written:" + ls "$ANDROID_HOME/licenses" + if [ -d "$ANDROID_HOME/platforms" ]; then + ls "$ANDROID_HOME/platforms" | sed 's/^/platform: /' + else + echo "WARN: no platforms directory under ANDROID_HOME" + fi + if [ -d "$ANDROID_HOME/build-tools" ]; then + ls "$ANDROID_HOME/build-tools" | sed 's/^/build-tools: /' + else + echo "WARN: no build-tools directory under ANDROID_HOME" + fi + + # assembleRelease needs NO signing config for a library (AARs are never + # signed). Builds only :android to keep the run scoped and fast. + - name: Assemble android AAR and fat jar + run: | + export PATH="$JAVA_HOME/bin:$PATH" + chmod +x ./gradlew + ./gradlew :android:assembleRelease :android:androidReleaseJar --parallel + + # Also builds the sample app (includes sample app APK, android/sample). + # Activates when the sample-app lane lands: :android:sample is not yet in + # settings.gradle.kts, so this step fails (intentionally, no always-pass + # guard) until that lane adds android/sample/build.gradle.kts + the + # settings include. Kept plain so it is a real gate, not a no-op. + - name: Assemble sample app (includes sample app) + run: | + export PATH="$JAVA_HOME/bin:$PATH" + chmod +x ./gradlew + ./gradlew :android:sample:assembleDebug --parallel + + - name: Stop Gradle daemons + if: always() + # Scoped cleanup: `gradlew --stop` stops only the daemons registered + # under this Gradle user home/version. Never a blanket java kill on a + # shared self-hosted runner. + run: | + export PATH="$JAVA_HOME/bin:$PATH" + ./gradlew --stop || true + + - name: Upload release AAR + if: always() + uses: actions/upload-artifact@v4 + with: + name: android-release-aar + path: android/build/outputs/aar/*.aar + if-no-files-found: warn + + # Fat classes jar for consumers running their own dex step (the + # monaco-lsp-demo classes.dex pipeline). Non-fatal: the AAR above is the + # recommended artifact. + - name: Upload fat classes jar + if: always() + uses: actions/upload-artifact@v4 + with: + name: android-release-fat-jar + path: android/build/libs/*.jar + if-no-files-found: warn + + - name: Upload sample APK + if: always() + uses: actions/upload-artifact@v4 + with: + name: androlua-sample-apk + path: android/sample/build/outputs/apk/debug/*.apk + if-no-files-found: warn diff --git a/.github/workflows/dex-verify.yml b/.github/workflows/dex-verify.yml new file mode 100644 index 00000000..25945cf8 --- /dev/null +++ b/.github/workflows/dex-verify.yml @@ -0,0 +1,91 @@ +# Linux self-hosted dex-slice runner: ONLY the dex-relevant jvmTest slices, +# kept fast (the full suite stays owned by the windows-full-jvmtest workflow). +# Modeled on windows-jvmtest.yml: self-hosted runner, workflow-level toolchain +# env, scoped daemon cleanup, non-fatal artifact uploads. +name: dex-jvmtest + +on: + workflow_dispatch: + push: + branches: [windows-verify, linux-verify] + +# Machine-specific toolchain locations for the self-hosted Linux runner. +# Kept at workflow level so the machine owner edits ONE place here. +env: + JAVA_HOME: /home/dingyi/.jdks/temurin-17 + ANDROID_HOME: /home/dingyi/Android/Sdk + +jobs: + dex-jvmtest: + runs-on: [self-hosted, linux, x64] + timeout-minutes: 45 + steps: + - name: Checkout + uses: actions/checkout@v4 + + # The dex slices include the real-fixture test against the demo corpus + # (DexRealFixtureTddTest walks up from user.dir to this exact path); the + # corpus is gitignored, so absence is informational (the fixture test + # soft-skips via JUnit Assume) — never a workflow failure. + - name: Verify dex fixture exists + run: | + if [ -f tools/monaco-lsp-demo/workspace/libs/classes.dex ]; then + echo "fixture-present" + else + echo "fixture-absent (gitignored demo corpus) — DexRealFixtureTddTest soft-skips" + fi + + - name: Show environment + run: | + export PATH="$JAVA_HOME/bin:$PATH" + echo "JAVA_HOME=$JAVA_HOME" + java -version + echo "ANDROID_HOME=$ANDROID_HOME" + git rev-parse --short HEAD + echo "mode=dex-slices gradle --parallel" + + # Dex-relevant slices only. Filters verified against real files: + # interop.dex.DexParserCommonTddTest + # -> src/commonTest/kotlin/interop/dex/DexParserCommonTddTest.kt + # interop.jvm.DexMountingTddTest + # -> src/jvmTest/kotlin/interop/jvm/DexMountingTddTest.kt + # interop.jvm.DexRealFixtureTddTest + # -> src/jvmTest/kotlin/interop/jvm/DexRealFixtureTddTest.kt + # io.github.dingyi222666.luaparser.interop.jvm.DexClassModelAdapterTest + # -> src/commonTest/kotlin/interop/jvm/DexClassModelAdapterTest.kt + # lsp.RuntimeJarTempLeakTddTest + # -> src/jvmTest/kotlin/lsp/RuntimeJarTempLeakTddTest.kt + - name: Run dex test slices + run: | + export PATH="$JAVA_HOME/bin:$PATH" + chmod +x ./gradlew + ./gradlew jvmTest --parallel \ + --tests "interop.dex.*" \ + --tests "interop.jvm.Dex*" \ + --tests "io.github.dingyi222666.luaparser.interop.jvm.DexClassModelAdapterTest" \ + --tests "lsp.RuntimeJarTempLeakTddTest" + + - name: Stop Gradle daemons + if: always() + # Scoped cleanup: `gradlew --stop` stops only the daemons registered + # under this Gradle user home/version. Never a blanket java kill on a + # shared self-hosted runner. + run: | + export PATH="$JAVA_HOME/bin:$PATH" + ./gradlew --stop || true + + - name: Upload JUnit XML + if: always() + uses: actions/upload-artifact@v4 + with: + name: dex-junit-xml + path: build/test-results/jvmTest/** + if-no-files-found: warn + + - name: Upload HTML report + if: always() + uses: actions/upload-artifact@v4 + with: + name: dex-test-report-html + path: build/reports/tests/jvmTest/** + if-no-files-found: warn diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..68db3516 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,51 @@ +# MANUAL release publishing to Maven Central. +# +# This workflow never runs automatically: it is workflow_dispatch only +# ("Run workflow" button). Nothing publishes on push or tag. +# +# VERSION BUMP GUARD — read before every run: +# Maven Central forbids re-uploading an existing (group, artifact, version): +# a failed or partial upload permanently burns that version. Before EVERY +# publish, bump `publishVersion` in build.gradle.kts (the single source of +# truth for the published coordinates) and commit the bump. If the version +# about to be published was already uploaded — even partially — STOP and +# bump again instead of retrying. +# +# SECRETS (placeholders only — no real values live in this file). Create them +# in Repo Settings > Secrets and variables > Actions: +# MAVEN_CENTRAL_USERNAME / MAVEN_CENTRAL_PASSWORD : Central (S01) portal token +# SIGNING_KEY : ASCII-armored private signing key (keep headers/newlines) +# SIGNING_PASSWORD : passphrase of the signing key +# They are exposed to Gradle as project properties via the ORG_GRADLE_PROJECT_ +# prefix, which is how the com.vanniktech.maven.publish plugin (configured in +# build.gradle.kts: SonatypeHost.S01 + in-memory signing) picks them up. +name: publish + +on: + workflow_dispatch: + +jobs: + publish: + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 17 + + # Caches ~/.gradle (dependencies, wrapper, build cache) between runs. + - name: Set up Gradle caching + uses: gradle/actions/setup-gradle@v4 + + - name: Publish to Maven Central + env: + ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SIGNING_KEY }} + ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SIGNING_PASSWORD }} + run: ./gradlew publishToMavenCentral --no-daemon --stacktrace diff --git a/.github/workflows/windows-full-jvmtest.yml b/.github/workflows/windows-full-jvmtest.yml new file mode 100644 index 00000000..5a631950 --- /dev/null +++ b/.github/workflows/windows-full-jvmtest.yml @@ -0,0 +1,96 @@ +# Windows self-hosted FULL jvmTest (tip-coherent, TASK-043 evidence). +# Does NOT use slice progress bar. CPU hard-capped (affinity 0x3F, workers=5). +# Branch: windows-verify only. Never main. +name: windows-full-jvmtest + +on: + workflow_dispatch: + push: + branches: [windows-verify] + paths: + - '.github/workflows/windows-full-jvmtest.yml' + - 'scripts/windows-run-full-jvmtest.ps1' + - 'scripts/windows-run-gradle-capped.ps1' + - 'scripts/windows-ensure-android-jar.ps1' + +# Machine-specific toolchain locations for the self-hosted Windows runner. +# Kept at workflow level so the machine owner edits ONE place here instead of +# hunting through job/step blocks. Same values the job-level env used before. +env: + JAVA_HOME: C:\Users\dingyi\.jdks\temurin-17.0.11 + ANDROID_HOME: C:\Users\dingyi\AppData\Local\Android\Sdk + +jobs: + full-jvmtest: + runs-on: [self-hosted, Windows, X64] + # Full suite can exceed slice timeouts; keep headroom under CPU cap. + timeout-minutes: 360 + env: + GRADLE_OPTS: -Dorg.gradle.workers.max=5 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Show environment + shell: pwsh + run: | + $env:Path = "$env:JAVA_HOME\bin;$env:Path" + Write-Host "JAVA_HOME=$env:JAVA_HOME" + & "$env:JAVA_HOME\bin\java.exe" -version + Write-Host "processor_count=$env:NUMBER_OF_PROCESSORS" + git rev-parse --short HEAD + Write-Host "mode=full-jvmtest cpu_hard_cap=affinity_0x3F workers=5" + + # Arm 174 soft-skips that need reflective android.jar (SDK platforms/android-35). + # Idempotent: no-op when jar already present. Never invents G:/. + - name: Ensure android.jar (platforms/android-35) + shell: pwsh + run: | + $env:Path = "$env:JAVA_HOME\bin;$env:Path" + ./scripts/windows-ensure-android-jar.ps1 + + - name: Full jvmTest (CPU hard-capped) + shell: pwsh + run: | + $env:Path = "$env:JAVA_HOME\bin;$env:Path" + ./scripts/windows-run-full-jvmtest.ps1 + + - name: Stop Gradle daemons + if: always() + shell: cmd + # Cleanup is deliberately scoped: `gradlew --stop` stops only the Gradle + # daemons registered under this Gradle user home/version. Do NOT add a + # blanket `taskkill /F /IM java.exe` here — on a shared self-hosted runner + # that kills every Java process machine-wide (IDEs, other runs' daemons, + # Kotlin compile daemons of concurrent jobs). Orphaned Kotlin compile + # daemons exit with the Gradle daemon or time out on their own. + run: | + set "PATH=%JAVA_HOME%\bin;%PATH%" + call gradlew.bat --stop + exit /b 0 + + - name: Upload JUnit XML + if: always() + uses: actions/upload-artifact@v4 + with: + name: full-junit-xml + path: build/test-results/jvmTest/** + if-no-files-found: warn + + - name: Upload full-jvmtest result + if: always() + uses: actions/upload-artifact@v4 + with: + name: full-jvmtest-result + path: | + full-jvmtest-result.json + tasks/agent-runs/full-jvmtest/** + if-no-files-found: warn + + - name: Upload HTML report + if: always() + uses: actions/upload-artifact@v4 + with: + name: full-test-report-html + path: build/reports/tests/jvmTest/** + if-no-files-found: warn diff --git a/.github/workflows/windows-jvmtest.yml b/.github/workflows/windows-jvmtest.yml new file mode 100644 index 00000000..1130e4ba --- /dev/null +++ b/.github/workflows/windows-jvmtest.yml @@ -0,0 +1,113 @@ +# Windows self-hosted slice runner (progress-bar mode). +# One small --tests group per run. Does NOT run full jvmTest. +# GATE: must-green-to-advance — red slice blocks next until fixed. +# Progress: tasks/agent-runs/win-slices/progress.json +name: windows-jvmtest + +on: + workflow_dispatch: + inputs: + slice: + description: 'Slice id from slices.json (empty = next non-success)' + required: false + default: '' + push: + branches: [windows-verify] + +# Machine-specific toolchain locations for the self-hosted Windows runner. +# Kept at workflow level so the machine owner edits ONE place here instead of +# hunting through job/step blocks. Same values the job-level env used before. +env: + JAVA_HOME: C:\Users\dingyi\.jdks\temurin-17.0.11 + ANDROID_HOME: C:\Users\dingyi\AppData\Local\Android\Sdk + +jobs: + jvm-slice: + runs-on: [self-hosted, Windows, X64] + timeout-minutes: 45 + env: + GRADLE_OPTS: -Dorg.gradle.workers.max=5 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Show environment + shell: pwsh + run: | + $env:Path = "$env:JAVA_HOME\bin;$env:Path" + Write-Host "JAVA_HOME=$env:JAVA_HOME" + & "$env:JAVA_HOME\bin\java.exe" -version + Write-Host "processor_count=$env:NUMBER_OF_PROCESSORS" + git rev-parse --short HEAD + Write-Host "mode=slice-progress cpu_hard_cap=affinity_0x3F workers=5" + if (Test-Path tasks/agent-runs/win-slices/progress.json) { + Get-Content tasks/agent-runs/win-slices/progress.json | Select-Object -First 40 + } + + # Arm reflective Android soft-skips when host SDK platform jar is missing. + - name: Ensure android.jar (platforms/android-35) + shell: pwsh + run: | + $env:Path = "$env:JAVA_HOME\bin;$env:Path" + ./scripts/windows-ensure-android-jar.ps1 + + - name: Compile test sources (CPU hard-capped) + shell: pwsh + run: | + $env:Path = "$env:JAVA_HOME\bin;$env:Path" + ./scripts/windows-run-gradle-capped.ps1 compileTestKotlinJvm --parallel --max-workers=5 + + - name: Run next test slice (CPU hard-capped) + shell: pwsh + env: + SLICE_INPUT: ${{ github.event.inputs.slice }} + run: | + $env:Path = "$env:JAVA_HOME\bin;$env:Path" + $slice = $env:SLICE_INPUT + if ([string]::IsNullOrWhiteSpace($slice)) { + ./scripts/windows-run-slice.ps1 + } else { + ./scripts/windows-run-slice.ps1 -Slice $slice + } + + - name: Stop Gradle daemons + if: always() + shell: cmd + # Cleanup is deliberately scoped: `gradlew --stop` stops only the Gradle + # daemons registered under this Gradle user home/version. Do NOT add a + # blanket `taskkill /F /IM java.exe` here — on a shared self-hosted runner + # that kills every Java process machine-wide (IDEs, other runs' daemons, + # Kotlin compile daemons of concurrent jobs). Orphaned Kotlin compile + # daemons exit with the Gradle daemon or time out on their own. + run: | + set "PATH=%JAVA_HOME%\bin;%PATH%" + call gradlew.bat --stop + exit /b 0 + + - name: Upload JUnit XML + if: always() + uses: actions/upload-artifact@v4 + with: + name: junit-xml + path: build/test-results/jvmTest/** + if-no-files-found: warn + + - name: Upload slice progress + if: always() + uses: actions/upload-artifact@v4 + with: + name: slice-progress + path: | + slice-result.json + slice-progress.json + tasks/agent-runs/win-slices/progress.json + tasks/agent-runs/win-slices/results/** + if-no-files-found: warn + + - name: Upload HTML report + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-report-html + path: build/reports/tests/jvmTest/** + if-no-files-found: warn diff --git a/.gitignore b/.gitignore index e9b3fac3..2638fec3 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,26 @@ src/main/kotlin-antlr .kotlin /kotlin-js-store/yarn.lock .idea/artifacts + +locks +tasks + +### Agent session state ### +.claude/ +scripts/watch-agent-activity.js +*_HANDOFF*.md + +### Node / demo tooling ### +node_modules/ +tools/monaco-lsp-demo/.lsp-classpath + +### Local build shims and crash dumps ### +gradlew.lf +gradlew.unix +gradlew.unix.tmp +sh.exe.stackdump +/org/ +tools/monaco-lsp-demo/workspace/libs/ +tools/monaco-lsp-demo/workspace/image/ +tools/monaco-lsp-demo/workspace/.using +.idea/ diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 00000000..1bec35e5 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 00000000..79ee123c --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 89022a78..45689754 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -5,6 +5,7 @@