mirror of
https://github.com/InsanusMokrassar/KSLog.git
synced 2026-11-02 01:32:54 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
29ada0d0ce |
No files matched your search
@@ -1,3 +0,0 @@
|
||||
*.kt text eol=lf
|
||||
kotlin text eol=lf
|
||||
kotlin.bat text eol=crlf
|
||||
@@ -1,60 +0,0 @@
|
||||
name: Build and test
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: build-${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
KOTLIN_CLI_NO_WELCOME_BANNER: "1"
|
||||
|
||||
jobs:
|
||||
validation:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-java@cf277c60eb25467037889841efdb72551f06f6c3 # v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "17"
|
||||
cache: gradle
|
||||
- uses: android-actions/setup-android@9fc6c4e9069bf8d3d10b2204b1fb8f6ef7065407 # v3
|
||||
with:
|
||||
packages: platform-tools
|
||||
- name: Install the declared Android SDK
|
||||
run: sdkmanager "platforms;android-37.0" "build-tools;37.0.0"
|
||||
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
|
||||
with:
|
||||
path: ~/.cache/JetBrains/Kotlin
|
||||
key: kotlin-toolchain-${{ runner.os }}-${{ hashFiles('kotlin', 'kslog/module.yaml', 'project.yaml', 'gradle/libs.versions.toml', 'plugins/**/*.yaml', 'plugins/**/*.kt') }}
|
||||
restore-keys: |
|
||||
kotlin-toolchain-${{ runner.os }}-
|
||||
- name: Check release/version consistency
|
||||
run: |
|
||||
python3 verification/check_release_consistency.py
|
||||
bash verification/test_changelog_parser.sh
|
||||
python3 verification/check_wrapper_line_endings.py
|
||||
- name: Load Kotlin Toolchain model
|
||||
run: ./kotlin show modules
|
||||
- name: Validate Gradle Dokka output overrides
|
||||
run: |
|
||||
./gradlew --no-daemon help --task dokkaHtml -PDOKKA_PATH="$RUNNER_TEMP/kslog-dokka-property"
|
||||
DOKKA_PATH="$RUNNER_TEMP/kslog-dokka-environment" ./gradlew --no-daemon help --task dokkaHtml
|
||||
- name: Build Kotlin Toolchain targets
|
||||
run: ./kotlin build
|
||||
- name: Run Kotlin Toolchain checks
|
||||
run: ./kotlin check
|
||||
- name: Build the complete retained Gradle fallback
|
||||
run: ./gradlew --no-daemon clean build
|
||||
- name: Verify isolated Gradle publication and consumers
|
||||
run: bash verification/verify_gradle_fallback.sh
|
||||
@@ -1,73 +1,21 @@
|
||||
name: Publish KDocs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: kdocs-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
KOTLIN_CLI_NO_WELCOME_BANNER: "1"
|
||||
|
||||
jobs:
|
||||
generate:
|
||||
publishing:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-java@v1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-java@cf277c60eb25467037889841efdb72551f06f6c3 # v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "17"
|
||||
- uses: android-actions/setup-android@9fc6c4e9069bf8d3d10b2204b1fb8f6ef7065407 # v3
|
||||
with:
|
||||
packages: platform-tools
|
||||
- name: Install the declared Android SDK
|
||||
run: sdkmanager "platforms;android-37.0" "build-tools;37.0.0"
|
||||
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
|
||||
with:
|
||||
path: ~/.cache/JetBrains/Kotlin
|
||||
key: kotlin-toolchain-${{ runner.os }}-${{ hashFiles('kotlin', 'kslog/module.yaml', 'project.yaml', 'gradle/libs.versions.toml', 'plugins/dokka/**/*.yaml', 'plugins/dokka/**/*.kt') }}
|
||||
restore-keys: |
|
||||
kotlin-toolchain-${{ runner.os }}-
|
||||
- name: Materialize platform analysis classpaths
|
||||
run: ./kotlin build -m kslog -p js -p wasmJs
|
||||
- name: Generate KDocs
|
||||
run: ./kotlin do dokkaHtml
|
||||
- name: Upload generated KDocs
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
with:
|
||||
name: kdocs-html
|
||||
path: build/dokka/html
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
deploy:
|
||||
needs: generate
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Download generated KDocs
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||
with:
|
||||
name: kdocs-html
|
||||
path: kdocs-html
|
||||
java-version: 17
|
||||
- name: Build
|
||||
run: ./gradlew dokkaHtml
|
||||
- name: Publish KDocs
|
||||
uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./kdocs-html
|
||||
publish_dir: ./build/dokka/html
|
||||
publish_branch: kdocs
|
||||
@@ -0,0 +1,29 @@
|
||||
name: Publish package to GitHub Packages
|
||||
on: [push]
|
||||
jobs:
|
||||
publishing:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 17
|
||||
- name: Rewrite version
|
||||
run: |
|
||||
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
|
||||
cat gradle.properties | sed -e "s/^version=\([0-9\.]*\)/version=\1-branch_$branch-build${{ github.run_number }}/" > gradle.properties.tmp
|
||||
rm gradle.properties
|
||||
mv gradle.properties.tmp gradle.properties
|
||||
- name: Build
|
||||
run: ./gradlew build
|
||||
- name: Publish to Gitea
|
||||
continue-on-error: true
|
||||
run: ./gradlew publishAllPublicationsToGiteaRepository
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
- name: Publish
|
||||
continue-on-error: true
|
||||
run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository
|
||||
env:
|
||||
GITHUBPACKAGES_USER: ${{ github.actor }}
|
||||
GITHUBPACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
|
||||
-148
@@ -1,148 +0,0 @@
|
||||
# Developing KSLog
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- JDK 17
|
||||
- Android SDK platform 37 and build tools 37.0.0
|
||||
(`sdkmanager` package IDs: `platforms;android-37.0` and `build-tools;37.0.0`;
|
||||
the library's `compileSdk` remains 37)
|
||||
- Git
|
||||
- Bash and Python 3 for the committed compatibility verifiers
|
||||
|
||||
The checked-in `kotlin` and `kotlin.bat` wrappers download the pinned Kotlin
|
||||
Toolchain distribution into the normal user cache. No global Toolchain install
|
||||
is required.
|
||||
|
||||
## Build and test
|
||||
|
||||
```shell
|
||||
./kotlin show modules
|
||||
./kotlin clean
|
||||
./kotlin build
|
||||
./kotlin check
|
||||
```
|
||||
|
||||
Build one supported platform with `./kotlin build -p <platform>`, where the
|
||||
declared platforms are `android`, `js`, `jvm`, `linuxArm64`, `linuxX64`,
|
||||
`mingwX64`, and `wasmJs`. Run the real JVM tests directly with:
|
||||
|
||||
```shell
|
||||
./kotlin test -p jvm
|
||||
```
|
||||
|
||||
Kotlin Toolchain 0.13.0 does not expose `linuxArm32Hfp`. Until that target and
|
||||
the existing `kslog-wasm-js` artifact name can be represented, the Gradle build
|
||||
remains the full-contract fallback:
|
||||
|
||||
```shell
|
||||
./gradlew --no-daemon build
|
||||
./gradlew --no-daemon compileKotlinLinuxArm32Hfp
|
||||
```
|
||||
|
||||
Do not remove the Gradle path merely because the supported Toolchain targets
|
||||
compile. See `MIGRATION_REPORT.md` for the exact capability gaps and validation
|
||||
matrix.
|
||||
|
||||
Run the release/version preflight independently with:
|
||||
|
||||
```shell
|
||||
python3 verification/check_release_consistency.py
|
||||
bash verification/test_changelog_parser.sh
|
||||
```
|
||||
|
||||
CI and the explicit GitHub release command run the same consistency check.
|
||||
|
||||
## Documentation
|
||||
|
||||
```shell
|
||||
./kotlin build -m kslog -p js -p wasmJs
|
||||
./kotlin do dokkaHtml
|
||||
./kotlin do dokkaGfm
|
||||
```
|
||||
|
||||
The outputs are `build/dokka/html` and `build/dokka/gfm`. CI only deploys HTML
|
||||
documentation from `master`; pull requests and other branch pushes never deploy
|
||||
documentation. Toolchain 0.13.0 exposes only the JVM module classpath to local
|
||||
plugins, so the platform build first materializes the transformed Common plus
|
||||
matching JS and WasmJS standard-library KLIBs in the Toolchain cache. The Dokka
|
||||
plugin discovers those KLIBs from the resolved JVM stdlib version and fails with
|
||||
the prebuild command above if any one is missing.
|
||||
|
||||
The legacy Gradle-only `DOKKA_PATH` property/environment override is not
|
||||
implemented by the Toolchain plugin. Toolchain documentation is deliberately
|
||||
restricted to generated directories under `build/dokka` so cleanup cannot
|
||||
target an arbitrary external path. Use the retained Gradle path when that
|
||||
legacy override is required.
|
||||
|
||||
## Local publication and compatibility testing
|
||||
|
||||
Publication is enabled for the KSLog module, while the two local build-plugin
|
||||
modules are intentionally not publishable. Select the library module explicitly:
|
||||
|
||||
```shell
|
||||
isolated_repo="$(mktemp -d)"
|
||||
KOTLIN_CLI_JAVA_OPTIONS="-Dmaven.repo.local=$isolated_repo" \
|
||||
KOTLIN_TOOLCHAIN_SIGNING_KEY="$(gpg --armor --export-secret-keys YOUR_KEY_ID)" \
|
||||
./kotlin publish -m kslog mavenLocal
|
||||
```
|
||||
|
||||
Use a throwaway key for local validation. Never use the command above without
|
||||
the isolated Maven path for version 2.1.0, because it would overwrite the normal
|
||||
local copy of that released version.
|
||||
|
||||
The retained Gradle publication can be checked without credentials or signing
|
||||
keys against an archive of the exact migration base:
|
||||
|
||||
```shell
|
||||
bash verification/verify_gradle_fallback.sh
|
||||
```
|
||||
|
||||
The verifier creates isolated Gradle user homes and Maven repositories, checks
|
||||
the baseline/current 46-file publication inventories, source JARs, required POM
|
||||
metadata, source bytes, JVM/Android public method and field signatures, module
|
||||
identities, and compiles the committed consumer probe. It never writes to the
|
||||
normal Maven local repository.
|
||||
|
||||
## Remote publication and release
|
||||
|
||||
The validation workflow used for pushes and pull requests never publishes
|
||||
packages or creates tags/releases. The separate `master`-only KDocs workflow
|
||||
intentionally deploys generated HTML to the `kdocs` branch after a read-only
|
||||
generation job succeeds. Toolchain remote package publication is disabled in
|
||||
the resolved model: the module exposes only `mavenLocal`, has no Inmo Nexus
|
||||
repository/credentials, and does not enable Maven Central. `mavenLocal` is not
|
||||
inherently isolated; without the `-Dmaven.repo.local=...` JVM override shown
|
||||
above it writes to the user's normal Maven Local repository. There is no
|
||||
configured Toolchain destination that uploads packages remotely.
|
||||
|
||||
Do not re-enable Toolchain Nexus or Central publication until both the missing
|
||||
Linux ARM32 target and the `kslog-wasm-js` coordinate have compatible
|
||||
implementations and the complete publication diff has been reviewed again.
|
||||
|
||||
The retained Gradle build is the only full-target remote publication path. Its
|
||||
existing release tasks remain explicit maintainer operations and are not run by
|
||||
CI:
|
||||
|
||||
```shell
|
||||
INMONEXUS_USER=... INMONEXUS_PASSWORD=... \
|
||||
./gradlew --no-daemon publishAllPublicationsToInmoNexusRepository
|
||||
|
||||
SONATYPE_USER=... SONATYPE_PASSWORD=... \
|
||||
./gradlew --no-daemon publishAllPublicationsToSonatypeRepository
|
||||
```
|
||||
|
||||
Those commands require the existing Gradle/GPG release configuration and cause
|
||||
real uploads. They are documented only to preserve the fallback release
|
||||
contract; do not run them as migration validation.
|
||||
|
||||
Preview the GitHub release payload without network mutation:
|
||||
|
||||
```shell
|
||||
KSLOG_GITHUB_RELEASE_DRY_RUN=true ./kotlin do githubRelease
|
||||
```
|
||||
|
||||
A real release requires `GITHUB_RELEASE_TOKEN`, `GH_TOKEN`, or `GITHUB_TOKEN`
|
||||
and an explicit `./kotlin do githubRelease` invocation. The command first runs
|
||||
the release/version consistency preflight, then delegates to the standard
|
||||
`gh release create` CLI. It may create the remote tag when that tag is absent;
|
||||
it does not alter local git state. Validation CI never invokes it.
|
||||
@@ -1,159 +0,0 @@
|
||||
# KSLog Gradle to Kotlin Toolchain migration plan
|
||||
|
||||
This inventory was written before adding any Kotlin Toolchain YAML. It records the
|
||||
Gradle contract at commit `0b9fb30862c7319bb228039ee33eef9a0ddef95c` and is the
|
||||
checklist for the migration.
|
||||
|
||||
## Pinned inputs
|
||||
|
||||
- Kotlin Toolchain release: `v0.13.0`, source commit
|
||||
`abc7a5f7579e9ef267107782048c76777f0bf932`.
|
||||
- Official wrapper from that tag: CLI distribution `0.13.0-dev-4444`, SHA-256
|
||||
`13c2448146ac6dba0fa3a4e5d367dab73d8cfd7400bab348bebe905c165adecf`.
|
||||
- Kotlin: `2.4.20` (set explicitly even though the release defaults to it).
|
||||
- Dokka: `2.2.0`, source commit
|
||||
`656ca46fbbd676d872b0bd383042fc12ae7adcdd`.
|
||||
- Migration guidance: the four official Kotlin agent skills installed from
|
||||
`Kotlin/kotlin-agent-skills@087f5689c1934135ed58718cdf524525e452b7e9`.
|
||||
|
||||
The pinned Toolchain documentation and source confirm that nested module
|
||||
templates are supported in 0.13.0. This resolves the contradictory statement in
|
||||
the core agent skill, although this one-module project does not need a template
|
||||
hierarchy.
|
||||
|
||||
## Existing project contract
|
||||
|
||||
### Source and API
|
||||
|
||||
- One Kotlin Multiplatform library with 20 production Kotlin files and no tests.
|
||||
- Source sets: `commonMain`, `jvmMain`, `androidMain`, `jsMain`, `wasmJsMain`,
|
||||
and `nativeMain`.
|
||||
- Production source bytes must remain unchanged. Moving source roots to the
|
||||
stable `kslog` Toolchain module's KMP layout is allowed; legacy Gradle
|
||||
source-set paths will be mapped to those directories while the compatibility
|
||||
fallback remains.
|
||||
- Public package/API/behavior must not change.
|
||||
|
||||
### Targets and execution environments
|
||||
|
||||
| Existing declaration | Required Toolchain mapping | Status in Toolchain 0.13.0 |
|
||||
|---|---|---|
|
||||
| JVM, bytecode 8 | `jvm`, target-specific `-jvm-target=1.8` | Supported; verified class major 52 |
|
||||
| Android, bytecode 17 | `android`, target-specific `-jvm-target=17` | Supported; verified class major 61 |
|
||||
| Android min/compile/build tools 21/37/37.0.0 | `settings.android` | Supported |
|
||||
| JS IR, browser + Node.js | `js` | Library compilation/publication supported; runtime selection is not configurable |
|
||||
| WasmJS, browser + Node.js + d8 | `wasmJs` | Library compilation/publication and browser tests supported; Node.js/d8 runners are not exposed |
|
||||
| linuxX64 | `linuxX64` | Supported |
|
||||
| linuxArm64 | `linuxArm64` | Supported (cross-compilation on this host) |
|
||||
| linuxArm32Hfp | none | **Blocked:** not present in the 0.13.0 platform schema |
|
||||
| mingwX64 | `mingwX64` | Supported (cross-compilation on this host) |
|
||||
| Shared native implementation | `src@native` | Supported |
|
||||
|
||||
The pinned `Platform` enum omits `linuxArm32Hfp`; a minimal model-load probe
|
||||
reports an empty effective platform list at that value. The probe remains
|
||||
outside the active project model and its result is recorded in
|
||||
`MIGRATION_REPORT.md`.
|
||||
|
||||
Toolchain 0.13.0 does not allow `settings.jvm.release` to vary by platform, so
|
||||
the common value is unset and the JVM/Android compiler targets are supplied as
|
||||
platform-specific Kotlin arguments. Inspected output proves the required
|
||||
bytecode levels.
|
||||
|
||||
### Build plugins and custom logic
|
||||
|
||||
| Gradle plugin/feature | Decision | Replacement/justification |
|
||||
|---|---|---|
|
||||
| Kotlin Multiplatform | Native | `product.type: kmp/lib` and explicit platforms |
|
||||
| Android KMP library plugin | Native | Android platform in the KMP product |
|
||||
| Maven Publish | Native for isolated local validation | `settings.publishing` plus the sole publish repository `mavenLocal`; Toolchain remotes stay disabled while parity is blocked |
|
||||
| NMCP aggregation/Central Portal | Retained in Gradle only | Toolchain `mavenCentral` remains at its disabled default; the complete Gradle fallback preserves the existing remote path |
|
||||
| Signing plugin/GPG command | Native | `signArtifacts` and Toolchain signing environment variables |
|
||||
| Dokka Gradle plugin | Local plugin | Vendor/extend the official Toolchain Dokka build plugin; add KMP source sets, HTML and GFM commands |
|
||||
| GitHub release Gradle plugin | Local plugin using standard `gh` CLI | Explicit `githubRelease` command, dry-run support, no automatic release |
|
||||
| Ben Manes versions plugin | Drop | Dependency maintenance only; Renovate remains |
|
||||
| Dexcount | Drop from Toolchain path | No library/API/publication output; the Toolchain has no native equivalent |
|
||||
| `javadocsJar` | Partial native publication behavior | Toolchain 0.13.0 publishes placeholder Javadoc JARs for platform artifacts but omits the root `kslog-2.1.0-javadoc.jar` |
|
||||
| `changelog_parser.sh` | Preserve | Used by the GitHub release command |
|
||||
|
||||
The official Toolchain Dokka plugin was reviewed first. It explicitly says it
|
||||
does not support KMP and only exposes HTML. The local adaptation keeps its
|
||||
Apache-2.0 headers and provenance, models the actual KSLog source-set graph, and
|
||||
adds the official Dokka GFM plugin. No existing Toolchain/GitHub-release plugin
|
||||
was found; `gh` is already installed and is the standard implementation used by
|
||||
the thin release action.
|
||||
|
||||
### Publication contract
|
||||
|
||||
- Group/version/base artifact: `dev.inmo:kslog:2.1.0`.
|
||||
- Existing variant artifact IDs:
|
||||
- `kslog-android`
|
||||
- `kslog-js`
|
||||
- `kslog-jvm`
|
||||
- `kslog-linuxarm32hfp`
|
||||
- `kslog-linuxarm64`
|
||||
- `kslog-linuxx64`
|
||||
- `kslog-mingwx64`
|
||||
- `kslog-wasm-js`
|
||||
- Publish KMP root metadata, per-platform artifacts, sources JARs, and empty
|
||||
javadoc JARs.
|
||||
- Preserve POM name/description/homepage, both developers, MIT license, and SCM
|
||||
URL/developer connection.
|
||||
- Preserve complete Inmo Nexus/Maven Central/signing capability through the
|
||||
retained Gradle fallback and preserve the explicit GitHub release command.
|
||||
Toolchain remote repositories stay absent/disabled until parity is restored.
|
||||
Never run a remote publication/release as migration validation.
|
||||
- Validate to an isolated Maven local directory via
|
||||
`-Dmaven.repo.local=<temporary path>`; never overwrite normal `~/.m2` artifacts.
|
||||
|
||||
Known 0.13.0 publication mismatch: built-in Toolchain publication derives the
|
||||
Wasm artifact suffix from `wasmJs` as `-wasmjs`, while Kotlin Gradle publishes
|
||||
`-wasm-js`. Together with the missing Linux ARM32 target, this prevents a
|
||||
publication-compatible cutover. The legacy Gradle files therefore remain until
|
||||
these two blockers are resolved; the Toolchain implementation is a reversible,
|
||||
working supported-target migration rather than a claim of full parity.
|
||||
|
||||
The stable `kslog` module directory plus narrow compiler-identity overrides
|
||||
preserve the existing JVM Kotlin module and KLIB unique/short names. Remaining
|
||||
built-in POM, Javadoc-placeholder, AAR-layout, and JS manifest-shape differences
|
||||
are itemized in `MIGRATION_REPORT.md`.
|
||||
|
||||
### CI and documentation contract
|
||||
|
||||
- Replace the stale push workflow that names nonexistent Gitea and
|
||||
GitHub-Packages Gradle tasks with build/check validation only.
|
||||
- Run the complete retained Gradle build plus an isolated baseline/current
|
||||
local-publication, public-signature, and consumer verifier in validation CI.
|
||||
- Push and pull-request validation must not publish artifacts, docs, tags, or
|
||||
releases.
|
||||
- Keep documentation deployment master-only and change generation to the
|
||||
Toolchain `dokkaHtml` command with output `build/dokka/html`.
|
||||
- Generate docs in a read-only job without persisted checkout credentials, then
|
||||
deploy the generated artifact in a separate write-enabled job.
|
||||
- Keep `dokkaGfm` with output `build/dokka/gfm` for local/consumer workflows.
|
||||
- Remote publication and GitHub release remain explicit manual commands and are
|
||||
documented, not automatically broadened.
|
||||
|
||||
## Implementation checklist
|
||||
|
||||
- [x] Add the checked-in 0.13.0 wrappers.
|
||||
- [x] Add `project.yaml`, stable `kslog/module.yaml`, and extend the existing version catalog.
|
||||
- [x] Move source roots byte-for-byte to Toolchain KMP layout.
|
||||
- [x] Map legacy Gradle source sets to the moved roots while blockers remain.
|
||||
- [x] Add focused JVM behavior tests so validation is not `NO-SOURCE`.
|
||||
- [x] Vendor and adapt the official Dokka plugin with source/commit/license.
|
||||
- [x] Add the narrow GitHub release plugin with a non-network dry-run.
|
||||
- [x] Load the project model and enumerate modules, platforms, commands, and checks.
|
||||
- [x] Clean-build every declared Toolchain-supported target.
|
||||
- [x] Run real tests/checks and distinguish host-unrunnable targets.
|
||||
- [x] Generate HTML and GFM docs at the preserved paths.
|
||||
- [x] Publish Toolchain artifacts with an ephemeral signing key to an isolated
|
||||
Maven repository before disabling all Toolchain remote destinations.
|
||||
- [x] Inspect artifacts, signatures, POMs, and Gradle module metadata.
|
||||
- [x] Add a committed verifier that republishes the untouched base and the
|
||||
post-move Gradle fallback with isolated Gradle homes and no signing secrets.
|
||||
- [x] Run Gradle KMP and Android consumer smoke tests against the isolated repository.
|
||||
- [x] Compare production source hashes and JVM/Android public bytecode/API with Gradle.
|
||||
- [x] Validate CI YAML, shell scripts, and `git diff --check`.
|
||||
- [x] Rebuild `ast-index` after source moves and edits.
|
||||
- [x] Record exact commands, results, logs, blockers, and remote-only checks in
|
||||
`MIGRATION_REPORT.md`.
|
||||
@@ -1,278 +0,0 @@
|
||||
# KSLog Kotlin Toolchain migration report
|
||||
|
||||
Date: 2026-11-02
|
||||
Base commit: `0b9fb30862c7319bb228039ee33eef9a0ddef95c`
|
||||
Kotlin Toolchain: `v0.13.0` source commit
|
||||
`abc7a5f7579e9ef267107782048c76777f0bf932`
|
||||
|
||||
## Result
|
||||
|
||||
KSLog now has a checked-in, release-pinned Kotlin Toolchain project that loads,
|
||||
builds every platform supported by Toolchain 0.13.0, runs real JVM tests,
|
||||
generates both documentation formats, can publish signed KMP artifacts through
|
||||
the configured `mavenLocal` destination, and is consumable by Gradle KMP and
|
||||
Android projects. Migration validation redirects Maven Local to a temporary
|
||||
repository; the destination itself is not inherently isolated. Toolchain Nexus
|
||||
credentials/repository configuration is absent and resolved Maven Central
|
||||
publishing is disabled.
|
||||
|
||||
This is **not yet a publication-compatible full cutover**. Toolchain 0.13.0
|
||||
cannot represent `linuxArm32Hfp` and hard-codes the WasmJS artifact suffix as
|
||||
`-wasmjs` instead of KSLog's released `-wasm-js`. The Gradle build is therefore
|
||||
retained, mapped to the moved sources, and validated as the complete target and
|
||||
publication fallback. No remote artifact, documentation, tag, or release was
|
||||
published during this migration.
|
||||
|
||||
## Implemented layout
|
||||
|
||||
- `kotlin` and `kotlin.bat` preserve the official 0.13.0 wrapper content. Git
|
||||
attributes enforce LF for `kotlin`/Kotlin sources and CRLF for `kotlin.bat`.
|
||||
The wrappers select CLI `0.13.0-dev-4444` with the official pinned
|
||||
distribution SHA-256.
|
||||
- `project.yaml` registers the stable `kslog` library module and two local build
|
||||
plugin modules. The stable directory name prevents compiler module identity
|
||||
from depending on the checkout/worktree directory name.
|
||||
- `kslog/module.yaml` declares Android, JS, JVM, WasmJS, Linux x64/ARM64, and
|
||||
MinGW x64, plus Android/JDK/Kotlin settings and full publication metadata.
|
||||
- The 20 production files moved byte-for-byte into `kslog/src*` Toolchain
|
||||
source roots. `build.gradle` maps the retained Gradle source sets to those
|
||||
roots, including Linux ARM32.
|
||||
- Compiler module identities are pinned to the existing published identities:
|
||||
JVM/Android `META-INF/dev.inmo_kslog.kotlin_module`, leaf KLIB
|
||||
`unique_name=dev.inmo:kslog`, common metadata `kslog_commonMain`, and native
|
||||
metadata `dev.inmo:kslog_nativeMain`/`kslog_nativeMain`.
|
||||
- `kslog/test@jvm/CallbackKSLogTest.kt` adds two focused tests covering exact
|
||||
callback forwarding and rejected lazy-message evaluation.
|
||||
- `plugins/dokka` adapts the official Apache-2.0 Toolchain Dokka plugin for
|
||||
KSLog's KMP graph and Dokka 2.2.0 HTML/GFM output. Each adapted Apache source
|
||||
carries a prominent KSLog modification/provenance notice, and cleanup rejects
|
||||
symbolic-link ancestors and requires real-path containment. Platform analysis
|
||||
receives JVM/common, JS, and WasmJS standard-library classpaths. Because the
|
||||
plugin API only exposes the JVM module classpath, the documented JS/WasmJS
|
||||
prebuild materializes the transformed Common and matching platform KLIBs for
|
||||
deterministic discovery. Generation
|
||||
fails before deployment if unresolved-type placeholders remain.
|
||||
- `plugins/github-release` provides an explicit `gh release create` command and
|
||||
a non-network dry-run mode. It runs the committed version consistency
|
||||
preflight first; no workflow invokes it.
|
||||
- `DEVELOPMENT.md` documents Toolchain use, the Gradle fallback, isolated local
|
||||
publication, disabled Toolchain remotes, the retained Gradle release path,
|
||||
and release gating.
|
||||
- `verification/` contains portable release/parser/wrapper checks plus the
|
||||
baseline/current Gradle publication, source-identity, public-signature, and
|
||||
consumer verifier used by CI.
|
||||
|
||||
## Target and test matrix
|
||||
|
||||
| Target | Toolchain result | Runtime/test result | Contract notes |
|
||||
|---|---|---|---|
|
||||
| JVM | PASS | 2 tests found, 2 started, 2 passed | Bytecode major 52; 25-class list and public JVM method/field signatures from `javap -public -s -constants` match Gradle |
|
||||
| Android | PASS | No device tests in the project; Android consumer compilation PASS | Bytecode major 61; 24-class list and public JVM method/field signatures match Gradle; minSdk 21, compileSdk/build tools 37 |
|
||||
| JS | PASS | No test sources; Toolchain 0.13.0 exposes no browser/Node runner selection for a library | Gradle fallback keeps browser + Node declarations |
|
||||
| WasmJS | PASS | No test sources; Toolchain 0.13.0 exposes no Node/d8 selection and only has browser test support | Gradle fallback keeps browser + Node + d8 declarations |
|
||||
| linuxX64 | PASS | No test sources | Host-compilable KLIB |
|
||||
| linuxArm64 | PASS | Cross-compiled; not runnable on this x64 host | KLIB publication produced |
|
||||
| mingwX64 | PASS | Cross-compiled; not runnable on this Linux host | KLIB publication produced |
|
||||
| linuxArm32Hfp | **UNSUPPORTED** | Gradle clean build compiles it; no tests | Absent from Toolchain 0.13.0 `Platform`; minimal model probe fails |
|
||||
|
||||
`./kotlin check` did not mistake empty platform suites for passing tests: the
|
||||
report only claims the two JVM tests. Gradle reports JS, WasmJS, Android, and
|
||||
native tests as `NO-SOURCE` or skipped, while still compiling all declared
|
||||
targets, including Linux ARM32.
|
||||
|
||||
## Verification evidence
|
||||
|
||||
The commands below are reproducible from the repository root. Temporary build
|
||||
logs and repositories are intentionally not part of the published report.
|
||||
|
||||
### Model, build, checks, and compatibility
|
||||
|
||||
| Command | Result |
|
||||
|---|---|
|
||||
| `./kotlin show modules` | PASS; modules `dokka`, `github-release`, `kslog` |
|
||||
| `./kotlin show settings -m kslog` plus `./kotlin show tasks` | PASS; `mavenCentral.enabled: false`, only local publish repository configured, no Nexus/Central task references |
|
||||
| `./kotlin show commands` / `show checks` | PASS; Dokka/release commands and builtin tests enumerated |
|
||||
| `./kotlin clean && ./kotlin build` | PASS; all seven Toolchain platforms plus both plugin modules |
|
||||
| `./kotlin check` | PASS; KSLog JVM tests 2/2 plus Dokka guard/classpath tests 4/4 |
|
||||
| `./kotlin test -m dokka -p jvm` | PASS 4/4; transformed/platform classpath mapping, unresolved-output rejection, symlink containment, and scoped cleanup |
|
||||
| `./gradlew --no-daemon clean build` | PASS; complete legacy build, including Linux ARM32 |
|
||||
| `bash verification/verify_gradle_fallback.sh` | PASS; isolated baseline/current Gradle publication and committed consumer probe |
|
||||
| `python3 verification/check_release_consistency.py` and parser/wrapper checks | PASS; four version copies, literal changelog heading, LF/CRLF attributes |
|
||||
| Linux ARM32 minimal model probe | Expected failure: `Product platforms list should not be empty` at `linuxArm32Hfp` |
|
||||
| GitHub YAML, actionlint 1.7.7, shell syntax, `git diff --check` | PASS |
|
||||
| `ast-index rebuild` | PASS after final edits |
|
||||
|
||||
The Toolchain emits the existing insignificant inline warning. It also warns
|
||||
that `-module-name`/`-Xshort-module-name` are passed more than once because the
|
||||
built-in checkout-derived names are intentionally overridden; inspection of
|
||||
the output JAR/KLIB manifests proves the last values match the released Gradle
|
||||
module identities.
|
||||
|
||||
### Source and ABI preservation
|
||||
|
||||
- All 20 production source files match the base commit byte-for-byte after
|
||||
their moves.
|
||||
- The JVM 25-class and Android 24-class lists match; public JVM method/field
|
||||
signature dumps from `javap -public -s -constants` have zero differences.
|
||||
- Gradle/Toolchain bytecode majors are JVM 52/52 and Android 61/61.
|
||||
- Published JVM, common/native metadata, and supported leaf KLIB module
|
||||
identities match Gradle.
|
||||
|
||||
These checks are deliberately narrow. Source identity, consumer compilation,
|
||||
JVM class/signature comparison, and inspected module names are not a complete
|
||||
Kotlin metadata or KLIB ABI proof. Full cutover remains blocked and requires a
|
||||
dedicated metadata/KLIB compatibility review in addition to resolving the two
|
||||
upstream capability gaps.
|
||||
|
||||
### Documentation and release command
|
||||
|
||||
| Command | Result |
|
||||
|---|---|
|
||||
| `./kotlin build -m kslog -p js -p wasmJs` | PASS; materializes the Common/JS/WasmJS analysis KLIBs required by the pinned plugin API |
|
||||
| `./kotlin do dokkaHtml` | PASS; 156 files at `build/dokka/html`, zero unresolved-type placeholders |
|
||||
| `./kotlin do dokkaGfm` | PASS; 101 files at `build/dokka/gfm`, zero unresolved-type placeholders |
|
||||
| Output/link/platform inspection | PASS; canonical source links and Common/JVM/Android/JS/WasmJS/Native tabs |
|
||||
| `KSLOG_GITHUB_RELEASE_DRY_RUN=true ./kotlin do githubRelease` | PASS; consistency preflight and `v2.1.0` payload, no network mutation |
|
||||
|
||||
The legacy `DOKKA_PATH` override is intentionally not ported. It remains
|
||||
available only through Gradle because Toolchain cleanup is restricted to direct
|
||||
children of `build/dokka`.
|
||||
|
||||
### Isolated publication and consumers
|
||||
|
||||
Toolchain publication used a temporary Maven repository selected with the JVM
|
||||
`maven.repo.local` override, `-m kslog`, and a generated throwaway signing key.
|
||||
The configured destination is named `mavenLocal` and would use the normal Maven
|
||||
Local repository without that override. The temporary private key was deleted
|
||||
after verification, and the normal Maven Local repository was not written.
|
||||
|
||||
| Check | Result |
|
||||
|---|---|
|
||||
| `./kotlin publish -m kslog mavenLocal` | PASS in an isolated repository |
|
||||
| Toolchain artifact/signature inventory | 40 primary files and 40 detached signatures; 40/40 signatures verified with the throwaway public key |
|
||||
| Toolchain required POM metadata comparison | PASS for root and every supported platform artifact |
|
||||
| Gradle KMP consumer compile | PASS for JVM, JS, WasmJS, linuxX64, linuxArm64, mingwX64 |
|
||||
| Gradle Android KMP consumer compile | PASS |
|
||||
| `bash verification/verify_gradle_fallback.sh` | PASS; untouched-base and post-move Gradle outputs each contain the same 46 primary files, nine source JARs, and nine required-metadata POMs; no signing task/key/config is used |
|
||||
|
||||
Produced IDs are `kslog`, `kslog-android`, `kslog-js`, `kslog-jvm`,
|
||||
`kslog-linuxarm64`, `kslog-linuxx64`, `kslog-mingwx64`, and `kslog-wasmjs`.
|
||||
Root Gradle module metadata, Kotlin Tooling metadata, and sources are present.
|
||||
Platform Toolchain publications contain placeholder Javadoc JARs, while the
|
||||
root Toolchain Javadoc JAR is absent. The committed Gradle verifier uses a fresh
|
||||
Gradle user home with only optional `caches`/`wrapper` links, an empty GnuPG
|
||||
home, sanitized credential/signing environment variables, and separate Maven
|
||||
repositories; it neither reads user Gradle signing configuration nor writes to
|
||||
the normal Maven local repository.
|
||||
|
||||
## Exact parity blockers and differences
|
||||
|
||||
1. **Linux ARM32:** `linuxArm32Hfp` is absent from the pinned 0.13.0
|
||||
`Platform` enum and schema. Adding it to `product.platforms` is rejected at
|
||||
model load, before compilation or publication can be customized.
|
||||
2. **Wasm artifact coordinate:** built-in KMP publication maps `wasmJs` to
|
||||
`kslog-wasmjs`; there is no per-platform artifact-suffix override. KSLog's
|
||||
released coordinate is `kslog-wasm-js`.
|
||||
3. **Runtime declarations:** JS browser/Node and WasmJS browser/Node/d8 runner
|
||||
selection are not exposed for this Toolchain library model. Compilation and
|
||||
publication work; those execution contracts stay in Gradle.
|
||||
4. **POM shape:** required name/description/URL/developers/license/SCM URL and
|
||||
developer connection are preserved. Toolchain additionally generates the
|
||||
SCM `connection`. Its root KMP POM uses `<packaging>pom</packaging>` and a
|
||||
`kslog-jvm` compile dependency, while Gradle's root POM carries a Kotlin
|
||||
stdlib runtime dependency. The Toolchain Android POM additionally declares
|
||||
`kotlin-stdlib-jdk7` and `kotlin-stdlib-jdk8`; its JS POM omits Gradle's
|
||||
`kotlin-dom-api-compat`; and its Linux ARM64, Linux x64, and MinGW POMs move
|
||||
the Kotlin stdlib dependency from compile to runtime scope. Gradle module
|
||||
metadata consumer selection and the committed consumers passed, but direct
|
||||
POM consumers can observe these dependency/scope changes, so this is not
|
||||
publication-shape parity and requires review before full cutover.
|
||||
5. **Root Javadoc artifact:** the 46-file Gradle publication has
|
||||
`kslog-2.1.0-javadoc.jar`; the 40-file Toolchain publication omits it. This is
|
||||
the sixth missing primary file in addition to the five Linux ARM32 files.
|
||||
Toolchain platform Javadoc JARs contain a manifest plus a 78-byte `README.md`
|
||||
placeholder, while Gradle's Javadoc JARs contain only the manifest.
|
||||
6. **Android manifest and AAR layout:** Gradle's manifest declares only
|
||||
`minSdkVersion=21`; Toolchain additionally emits `targetSdkVersion=37`.
|
||||
Gradle uses `classes.jar`; Toolchain uses
|
||||
`libs/kslog-jvm.jar`. Both are valid AAR layouts, the bytecode level and
|
||||
compared public JVM method/field signatures match, and an Android KMP
|
||||
consumer compiles successfully.
|
||||
7. **JS manifest detail:** public KLIB identity matches
|
||||
`unique_name=dev.inmo:kslog`, but Toolchain's JS/Wasm KLIB does not include
|
||||
Gradle's `jsOutputName=kslog` manifest entry.
|
||||
8. **Android resource exclusions:** `resourcePackaging` is not accepted for a
|
||||
Toolchain `kmp/lib`. The produced AAR contains only the manifest and library
|
||||
JAR, so none of the three legacy serialization metadata exclusions are
|
||||
currently present. Gradle keeps the exclusions for the fallback.
|
||||
9. **Per-platform JVM release:** Toolchain 0.13.0 does not allow `jvm.release`
|
||||
to differ by platform. The module uses target-specific `-jvm-target=1.8`
|
||||
and `-jvm-target=17`; inspected bytecode proves majors 52 and 61.
|
||||
10. **Documentation output override:** Gradle honors `DOKKA_PATH`; Toolchain
|
||||
intentionally does not because arbitrary external cleanup would weaken the
|
||||
generated-output containment guarantee.
|
||||
|
||||
Because items 1 and 2 change released target/coordinate contracts, remote
|
||||
Toolchain publication must remain blocked until Toolchain gains the required
|
||||
capabilities or an explicitly reviewed compatibility implementation is added.
|
||||
|
||||
## CI behavior
|
||||
|
||||
- `.github/workflows/build.yml` replaces the stale workflow that attempted
|
||||
nonexistent Gitea/GitHub-Packages Gradle tasks on every push. Pushes and pull
|
||||
requests now run release/parser/wrapper preflights, Toolchain build/check, the
|
||||
complete retained Gradle build, and the isolated publication/API/consumer
|
||||
verifier. Checkout fetches full history so the verifier's pinned migration
|
||||
base commit is available. It has read-only repository permissions and no
|
||||
secrets or remote publication commands.
|
||||
- `.github/workflows/kdocs.yml` remains `master`-only. Its read-only generation
|
||||
job checks out with `persist-credentials: false`, materializes the platform
|
||||
analysis KLIBs, runs Toolchain/Dokka, and
|
||||
uploads the generated artifact. A separate write-enabled job downloads only
|
||||
that artifact and deploys it to `kdocs`; project/plugin code never runs with a
|
||||
write credential.
|
||||
- All workflow actions are pinned to resolved commit SHAs.
|
||||
- Hosted CI status is reported on [draft PR #136](https://github.com/InsanusMokrassar/KSLog/pull/136); the local evidence above does not imply a hosted pass. SDK setup explicitly requests `platforms;android-37.0` (API 37) and excludes the removed legacy `tools` package.
|
||||
|
||||
## Remote-only checks not performed
|
||||
|
||||
- Inmo Nexus upload.
|
||||
- Maven Central bundle upload/validation/release.
|
||||
- GitHub release creation.
|
||||
- Documentation deployment to the `kdocs` branch.
|
||||
- Android device tests and browser/Node/d8 tests (there are currently no such
|
||||
test sources); Linux ARM64 and MinGW runtime execution on matching hosts.
|
||||
|
||||
## Recommended pull request
|
||||
|
||||
Suggested title:
|
||||
|
||||
> build: add Kotlin Toolchain migration with compatibility fallback
|
||||
|
||||
Suggested body:
|
||||
|
||||
> ## Summary
|
||||
> - add the pinned Kotlin Toolchain 0.13.0 wrappers and a stable `kslog` KMP module
|
||||
> - migrate source roots byte-for-byte and retain a validated Gradle fallback
|
||||
> - add real JVM tests, KMP-capable Dokka HTML/GFM tasks, and an explicit GitHub release command
|
||||
> - replace stale package-publishing CI with non-publishing Toolchain and full Gradle validation
|
||||
> - document isolated publication, release gating, and full verification evidence
|
||||
>
|
||||
> ## Validation
|
||||
> - all Toolchain-supported targets build
|
||||
> - JVM tests: 2/2 pass
|
||||
> - retained Gradle clean build passes, including linuxArm32Hfp
|
||||
> - JVM/Android class lists and public JVM method/field signature diffs are empty; bytecode remains 8/17
|
||||
> - signed isolated KMP publication verified and consumed by Gradle KMP + Android projects
|
||||
> - Dokka HTML/GFM and GitHub release dry-run pass
|
||||
>
|
||||
> ## Known blockers
|
||||
> Toolchain 0.13.0 has no linuxArm32Hfp platform and publishes `-wasmjs` instead
|
||||
> of the released `-wasm-js` coordinate. Therefore Gradle remains the publication
|
||||
> fallback and remote Toolchain publication is not enabled. See
|
||||
> `MIGRATION_REPORT.md` for the complete matrix and artifact differences.
|
||||
|
||||
This migration is ready only for a **draft PR**. Full Gradle removal is not
|
||||
review-ready and remains blocked by Toolchain 0.13.0's missing Linux ARM32
|
||||
platform and incompatible Wasm artifact ID; the narrow checks above do not
|
||||
waive those requirements.
|
||||
@@ -91,26 +91,3 @@ implementation "dev.inmo:kslog:$kslog_version"
|
||||
<version>${kslog_version}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Building from source
|
||||
|
||||
The checked-in `kotlin`/`kotlin.bat` wrappers pin JetBrains Kotlin Toolchain
|
||||
0.13.0. With JDK 17 and Android SDK 37 installed, the main validation commands
|
||||
are:
|
||||
|
||||
```shell
|
||||
./kotlin build
|
||||
./kotlin check
|
||||
./kotlin do dokkaHtml
|
||||
```
|
||||
|
||||
Kotlin Toolchain 0.13.0 cannot model the existing `linuxArm32Hfp` target and
|
||||
derives `kslog-wasmjs` instead of the released `kslog-wasm-js` artifact ID.
|
||||
The Gradle build is retained as a compatibility path until those publication
|
||||
contracts can be represented without dropping a target or changing consumer
|
||||
coordinates.
|
||||
|
||||
See [DEVELOPMENT.md](DEVELOPMENT.md) for target-specific commands, documentation
|
||||
generation, and explicitly gated publishing/release procedures. The migration
|
||||
inventory and verification evidence are in [MIGRATION_PLAN.md](MIGRATION_PLAN.md)
|
||||
and [MIGRATION_REPORT.md](MIGRATION_REPORT.md).
|
||||
@@ -82,39 +82,21 @@ kotlin {
|
||||
|
||||
sourceSets {
|
||||
commonMain {
|
||||
kotlin.srcDir("kslog/src")
|
||||
dependencies {
|
||||
implementation libs.kotlin
|
||||
}
|
||||
}
|
||||
commonTest {
|
||||
kotlin.srcDir("kslog/test")
|
||||
dependencies {
|
||||
implementation libs.kotlin.test.common
|
||||
implementation libs.kotlin.test.annotations.common
|
||||
}
|
||||
}
|
||||
androidMain {
|
||||
kotlin.srcDir("kslog/src@android")
|
||||
}
|
||||
jsMain {
|
||||
kotlin.srcDir("kslog/src@js")
|
||||
}
|
||||
jvmMain {
|
||||
kotlin.srcDir("kslog/src@jvm")
|
||||
}
|
||||
jvmTest {
|
||||
kotlin.srcDir("kslog/test@jvm")
|
||||
dependencies {
|
||||
implementation libs.kotlin.test.junit
|
||||
}
|
||||
}
|
||||
nativeMain {
|
||||
kotlin.srcDir("kslog/src@native")
|
||||
}
|
||||
wasmJsMain {
|
||||
kotlin.srcDir("kslog/src@wasmJs")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-19
@@ -1,32 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
function parse() {
|
||||
local version="$1"
|
||||
local line
|
||||
local found=""
|
||||
version="$1"
|
||||
|
||||
while IFS= read -r line; do
|
||||
if [[ "$line" == "## $version" ]]; then
|
||||
found="yes"
|
||||
break
|
||||
fi
|
||||
while IFS= read -r line && [ -z "`echo "$line" | grep -e "^#\+ $version"`" ]
|
||||
do
|
||||
: # do nothing
|
||||
done
|
||||
|
||||
if [[ -z "$found" ]]; then
|
||||
printf 'No literal changelog heading found for %s\n' "$version" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
while IFS= read -r line; do
|
||||
if [[ "$line" == \#* ]]; then
|
||||
break
|
||||
fi
|
||||
printf '%s\n' "$line"
|
||||
while IFS= read -r line && [ -z "`echo "$line" | grep -e "^#\+"`" ]
|
||||
do
|
||||
echo "$line"
|
||||
done
|
||||
}
|
||||
|
||||
version="$1"
|
||||
file="${2:-}"
|
||||
file="$2"
|
||||
|
||||
if [ -n "$file" ]; then
|
||||
parse "$version" < "$file"
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
Object callback = {
|
||||
switch (true) {
|
||||
case project.hasProperty("DOKKA_PATH"):
|
||||
outputDirectory.set(file(project.property("DOKKA_PATH").toString()))
|
||||
outputDirectory = project.property("DOKKA_PATH").toString()
|
||||
break
|
||||
case System.getenv("DOKKA_PATH") != null:
|
||||
outputDirectory.set(file(System.getenv("DOKKA_PATH")))
|
||||
outputDirectory = System.getenv("DOKKA_PATH")
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[versions]
|
||||
|
||||
kotlin = "2.4.20"
|
||||
kotlin = "2.4.21"
|
||||
android-gradle-plugin = "9.1.1"
|
||||
|
||||
nmcp = "1.6.2"
|
||||
@@ -37,14 +37,6 @@ kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", v
|
||||
kotlin-dokka-plugin = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" }
|
||||
github-release = { module = "com.github.breadmoirai:github-release", version.ref = "github-release" }
|
||||
|
||||
# Kotlin Toolchain local Dokka plugin
|
||||
dokka-cli = { module = "org.jetbrains.dokka:dokka-cli", version.ref = "dokka" }
|
||||
dokka-base = { module = "org.jetbrains.dokka:dokka-base", version.ref = "dokka" }
|
||||
dokka-analysis-kotlin-descriptors = { module = "org.jetbrains.dokka:analysis-kotlin-descriptors", version.ref = "dokka" }
|
||||
dokka-gfm = { module = "org.jetbrains.dokka:gfm-plugin", version.ref = "dokka" }
|
||||
kotlinx-html-jvm = "org.jetbrains.kotlinx:kotlinx-html-jvm:0.8.0"
|
||||
freemarker = "org.freemarker:freemarker:2.3.31"
|
||||
|
||||
[plugins]
|
||||
|
||||
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
|
||||
|
||||
@@ -1,286 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
#
|
||||
|
||||
# Possible environment variables:
|
||||
# KOTLIN_CLI_DOWNLOAD_ROOT Maven repository to download the Kotlin CLI dist from.
|
||||
# default: https://packages.jetbrains.team/maven/p/amper/amper
|
||||
# KOTLIN_CLI_JRE_DOWNLOAD_ROOT Url prefix to download the JRE to run the Kotlin CLI
|
||||
# default: https:/
|
||||
# KOTLIN_CLI_BOOTSTRAP_CACHE_DIR Cache directory to store the extracted JRE and Kotlin CLI distribution
|
||||
# KOTLIN_CLI_JAVA_HOME JRE to run the Kotlin CLI itself (optional, does not affect compilation)
|
||||
# KOTLIN_CLI_JAVA_OPTIONS JVM options to pass to the JVM running the Kotlin CLI (does not affect the user's application)
|
||||
# KOTLIN_CLI_NO_WELCOME_BANNER Disables the first-run welcome message if set to a non-empty value
|
||||
|
||||
set -e -u
|
||||
|
||||
# The version of the Kotlin Toolchain (and CLI) distribution to provision and use
|
||||
kotlin_cli_version=0.13.0-dev-4444
|
||||
# Establish chain of trust from here by specifying exact checksum of Kotlin Toolchain (and CLI) distribution to be run
|
||||
kotlin_cli_sha256=13c2448146ac6dba0fa3a4e5d367dab73d8cfd7400bab348bebe905c165adecf
|
||||
|
||||
KOTLIN_CLI_DOWNLOAD_ROOT="${KOTLIN_CLI_DOWNLOAD_ROOT:-https://packages.jetbrains.team/maven/p/amper/amper}"
|
||||
|
||||
die() {
|
||||
echo >&2
|
||||
echo "$@" >&2
|
||||
echo >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# usage: check_sha SOURCE_MONIKER FILE SHA_CHECKSUM SHA_SIZE
|
||||
# $1 SOURCE_MONIKER (e.g. url)
|
||||
# $2 FILE
|
||||
# $3 SHA hex string
|
||||
# $4 SHA size in bits (256, 512, ...)
|
||||
check_sha() {
|
||||
sha_size=$4
|
||||
if command -v shasum >/dev/null 2>&1; then
|
||||
echo "$3 *$2" | shasum -a "$sha_size" --status -c || {
|
||||
die "ERROR: Checksum mismatch for $2 (downloaded from $1): expected checksum $3 but got $(shasum --binary -a "$sha_size" "$2" | awk '{print $1}')"
|
||||
}
|
||||
return 0
|
||||
fi
|
||||
|
||||
shaNsumCommand="sha${sha_size}sum"
|
||||
if command -v "$shaNsumCommand" >/dev/null 2>&1; then
|
||||
# discard the output as sha*sum may print redundant warnings in some versions
|
||||
echo "$3 *$2" | $shaNsumCommand -w -c >/dev/null 2>&1 || {
|
||||
die "ERROR: Checksum mismatch for $2 (downloaded from $1): expected checksum $3 but got $($shaNsumCommand "$2" | awk '{print $1}')"
|
||||
}
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "Both 'shasum' and 'sha${sha_size}sum' utilities are missing. Please install one of them"
|
||||
return 1
|
||||
}
|
||||
|
||||
|
||||
download_and_extract() {
|
||||
moniker="$1"
|
||||
file_url="$2"
|
||||
file_sha="$3"
|
||||
sha_size="$4"
|
||||
cache_dir="$5"
|
||||
extract_dir="$6"
|
||||
show_banner_on_cache_miss="$7"
|
||||
|
||||
if [ -e "$extract_dir/.flag" ] && [ "$(cat "$extract_dir/.flag")" = "${file_sha}" ]; then
|
||||
# Everything is up-to-date in $extract_dir, do nothing
|
||||
return 0;
|
||||
fi
|
||||
|
||||
mkdir -p "$cache_dir"
|
||||
|
||||
# Take a lock for the download of this file
|
||||
short_sha=$(echo "$file_sha" | cut -c1-32) # cannot use the ${short_sha:0:32} syntax in regular /bin/sh
|
||||
download_lock_file="$cache_dir/download-${short_sha}.lock"
|
||||
process_lock_file="$cache_dir/download-${short_sha}.$$.lock"
|
||||
echo $$ >"$process_lock_file"
|
||||
while ! ln "$process_lock_file" "$download_lock_file" 2>/dev/null; do
|
||||
lock_owner=$(cat "$download_lock_file" 2>/dev/null || true)
|
||||
# We use `kill -0` instead of `ps -p` as the first one is more portable
|
||||
if [ -n "$lock_owner" ] && kill -0 "$lock_owner" >/dev/null; then
|
||||
echo "Another Kotlin CLI instance (pid $lock_owner) is downloading $moniker. Awaiting the result..."
|
||||
sleep 1
|
||||
elif [ -n "$lock_owner" ] && [ "$(cat "$download_lock_file" 2>/dev/null)" = "$lock_owner" ]; then
|
||||
rm -f "$download_lock_file"
|
||||
# We don't want to simply loop again here, because multiple concurrent processes may face this at the same time,
|
||||
# which means the 'rm' command above from another script could delete our new valid lock file. Instead, we just
|
||||
# ask the user to try again. This doesn't 100% eliminate the race, but the probability of issues is drastically
|
||||
# reduced because it would involve 4 processes with perfect timing. We can revisit this later.
|
||||
die "Another Kotlin CLI instance (pid $lock_owner) locked the download of $moniker, but is no longer running. The lock file is now removed, please try again."
|
||||
fi
|
||||
done
|
||||
|
||||
# shellcheck disable=SC2064
|
||||
trap "rm -f \"$download_lock_file\"" EXIT
|
||||
rm -f "$process_lock_file"
|
||||
|
||||
unlock_and_cleanup() {
|
||||
rm -f "$download_lock_file"
|
||||
trap - EXIT
|
||||
return 0
|
||||
}
|
||||
|
||||
if [ -e "$extract_dir/.flag" ] && [ "$(cat "$extract_dir/.flag")" = "${file_sha}" ]; then
|
||||
# Everything is up-to-date in $extract_dir, just release the lock
|
||||
unlock_and_cleanup
|
||||
return 0;
|
||||
fi
|
||||
|
||||
if [ "$show_banner_on_cache_miss" = "true" ] && [ -z "${KOTLIN_CLI_NO_WELCOME_BANNER:-}" ]; then
|
||||
echo
|
||||
cat <<EOF
|
||||
Welcome to
|
||||
|
||||
@@@ @@@@ @@@ @@@
|
||||
@@@ @@@@ @@@ @@@ @@@
|
||||
@@@ #@@@" ,@@@ @@@
|
||||
@@@ ,@@@% ,@@@@@@@, @@@@@@@@@@ @@@ @@@ @@@ ,@@@@@,
|
||||
@@@ @@@@ @@@@@%"%@@@@@ @@@@@@@@@@ @@@ @@@ @@@@@@%%@@@@@
|
||||
@@@@@@% @@@% %@@@ @@@ @@@ @@@ @@@@ %@@%
|
||||
@@@ @@@@= #@@@ @@@# @@@ @@@ @@@ @@@ @@@
|
||||
@@@ @@@@ #@@@ @@@# @@@ @@@ @@@ @@@ @@@
|
||||
@@@ *@@@% @@@@ @@@@ @@@ @@@ @@@ @@@ @@@
|
||||
@@@ %@@@= %@@@@*,*@@@@% @@@@### @@@ @@@ @@@ @@@
|
||||
@@@ @@@@ "@@@@@@@" %@@@@@ @@@ @@@ @@@ @@@
|
||||
EOF
|
||||
echo
|
||||
echo "This is the first run of the Kotlin CLI v$kotlin_cli_version, so we need to download the Kotlin Toolchain."
|
||||
echo "Please give us a few seconds, subsequent runs will be faster."
|
||||
echo
|
||||
fi
|
||||
|
||||
echo "Downloading $moniker..."
|
||||
|
||||
temp_file="$cache_dir/download-file-$$.bin"
|
||||
rm -f "$temp_file"
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
if [ -t 1 ]; then CURL_PROGRESS="--progress-bar"; else CURL_PROGRESS="--silent --show-error"; fi
|
||||
# shellcheck disable=SC2086
|
||||
curl $CURL_PROGRESS -L --fail --retry 5 --connect-timeout 30 --output "${temp_file}" "$file_url"
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
if [ -t 1 ]; then WGET_PROGRESS=""; else WGET_PROGRESS="-nv"; fi
|
||||
wget $WGET_PROGRESS --tries=5 --connect-timeout=30 --read-timeout=120 -O "${temp_file}" "$file_url"
|
||||
else
|
||||
die "ERROR: Please install 'wget' or 'curl', as one of them is required to download $moniker"
|
||||
fi
|
||||
|
||||
check_sha "$file_url" "$temp_file" "$file_sha" "$sha_size"
|
||||
|
||||
rm -rf "$extract_dir"
|
||||
mkdir -p "$extract_dir"
|
||||
|
||||
case "$file_url" in
|
||||
*".zip")
|
||||
if command -v unzip >/dev/null 2>&1; then
|
||||
unzip -q "$temp_file" -d "$extract_dir"
|
||||
else
|
||||
die "ERROR: Please install 'unzip', which is required to extract $moniker"
|
||||
fi ;;
|
||||
*)
|
||||
if command -v tar >/dev/null 2>&1; then
|
||||
tar -x -f "$temp_file" -C "$extract_dir"
|
||||
else
|
||||
die "ERROR: Please install 'tar', which is required to extract $moniker"
|
||||
fi ;;
|
||||
esac
|
||||
|
||||
rm -f "$temp_file"
|
||||
|
||||
echo "$file_sha" >"$extract_dir/.flag"
|
||||
|
||||
# Unlock and cleanup the lock file
|
||||
unlock_and_cleanup
|
||||
|
||||
echo "Download complete."
|
||||
echo
|
||||
}
|
||||
|
||||
|
||||
# ********** Project-local version detection **********
|
||||
|
||||
# 1. Search upwards for an executable `amper` file and/or `project.yaml`
|
||||
# Sets wrapper_script to the found wrapper path, or empty string if not found.
|
||||
find_project_context() {
|
||||
wrapper_script=""
|
||||
this_script="$(realpath "$0")"
|
||||
project_dir=$(pwd)
|
||||
while [ "$project_dir" != "/" ] && [ -n "$project_dir" ]; do
|
||||
wrapper_candidate="$project_dir/kotlin"
|
||||
if [ "$this_script" = "$wrapper_candidate" ]; then
|
||||
# Found itself (local wrapper case), no need to update any version or search further.
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ -f "$wrapper_candidate" ] && [ -x "$wrapper_candidate" ]; then
|
||||
# Found the wrapper — check that a project context exists alongside it
|
||||
if [ -f "$project_dir/project.yaml" ] || [ -f "$project_dir/module.yaml" ]; then
|
||||
wrapper_script="$wrapper_candidate"
|
||||
return 0
|
||||
else
|
||||
echo "WARNING: Found wrapper script '$wrapper_candidate', but no project.yaml or module.yaml near it. Skipping." >&2
|
||||
# Continue the search
|
||||
fi
|
||||
elif [ -f "$project_dir/project.yaml" ]; then
|
||||
# Found project.yaml but no executable wrapper alongside it
|
||||
echo "WARNING: Found a project.yaml in '$project_dir', but the wrapper script is missing; using Kotlin Toolchain v$kotlin_cli_version." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
project_dir=$(dirname "$project_dir")
|
||||
done
|
||||
# Do not check root '/' - it's an unlikely candidate for a project
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
parse_project_context() {
|
||||
# Parse kotlin_cli_version and kotlin_cli_sha256 from "$wrapper_script" without executing it.
|
||||
parsed_kotlin_cli_version=$(
|
||||
sed -n 's/^kotlin_cli_version=\([A-Za-z0-9._+-]\{1,\}\)[[:space:]]*$/\1/p' "$wrapper_script" \
|
||||
| head -n 1
|
||||
)
|
||||
parsed_kotlin_cli_sha256=$(
|
||||
sed -n 's/^kotlin_cli_sha256=\([0-9a-fA-F]\{64\}\)[[:space:]]*$/\1/p' "$wrapper_script" \
|
||||
| head -n 1
|
||||
)
|
||||
|
||||
if [ -z "$parsed_kotlin_cli_version" ]; then
|
||||
echo "ERROR: Suspicious local wrapper script: failed to detect the distribution version in '$wrapper_script'" >&2
|
||||
return 1
|
||||
fi
|
||||
if [ -z "$parsed_kotlin_cli_sha256" ]; then
|
||||
echo "ERROR: Suspicious local wrapper script: failed to detect the distribution checksum in '$wrapper_script'" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# overwrite builtin values and proceed
|
||||
kotlin_cli_version=$parsed_kotlin_cli_version
|
||||
kotlin_cli_sha256=$parsed_kotlin_cli_sha256
|
||||
return 0
|
||||
}
|
||||
|
||||
if [ -z "${KOTLIN_CLI_WRAPPER_ALWAYS_USE_INTRINSIC_VERSION:-}" ]; then
|
||||
find_project_context && parse_project_context
|
||||
fi
|
||||
|
||||
# ********** System detection **********
|
||||
|
||||
kernelName=$(uname -s)
|
||||
case "$kernelName" in
|
||||
Darwin* )
|
||||
default_kotlin_cli_cache_dir="$HOME/Library/Caches/JetBrains/Kotlin/cli"
|
||||
;;
|
||||
Linux* )
|
||||
default_kotlin_cli_cache_dir="$HOME/.cache/JetBrains/Kotlin/cli"
|
||||
;;
|
||||
CYGWIN* | MSYS* | MINGW* )
|
||||
if command -v cygpath >/dev/null 2>&1; then
|
||||
default_kotlin_cli_cache_dir=$(cygpath -u "$LOCALAPPDATA\JetBrains\Kotlin\cli")
|
||||
else
|
||||
die "The 'cypath' command is not available, but the Kotlin CLI needs it. Use kotlin.bat instead, or try a Cygwin or MSYS environment."
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
die "Unsupported platform $kernelName"
|
||||
;;
|
||||
esac
|
||||
|
||||
kotlin_cli_cache_dir="${KOTLIN_CLI_BOOTSTRAP_CACHE_DIR:-$default_kotlin_cli_cache_dir}"
|
||||
|
||||
# ********** Provision the Kotlin Toolchain distribution **********
|
||||
|
||||
kotlin_cli_url="$KOTLIN_CLI_DOWNLOAD_ROOT/org/jetbrains/kotlin/kotlin-cli/$kotlin_cli_version/kotlin-cli-$kotlin_cli_version-dist.tgz"
|
||||
kotlin_cli_target_dir="$kotlin_cli_cache_dir/kotlin-cli-$kotlin_cli_version"
|
||||
download_and_extract "Kotlin Toolchain distribution v$kotlin_cli_version" "$kotlin_cli_url" "$kotlin_cli_sha256" 256 "$kotlin_cli_cache_dir" "$kotlin_cli_target_dir" "true"
|
||||
|
||||
# ********** Launch the Kotlin CLI **********
|
||||
|
||||
launcher_script="$kotlin_cli_target_dir/bin/launcher.sh"
|
||||
|
||||
KOTLIN_CLI_WRAPPER_PATH="$(realpath "$0")" \
|
||||
exec /bin/sh "$launcher_script" "$@"
|
||||
-265
@@ -1,265 +0,0 @@
|
||||
@echo off
|
||||
|
||||
@rem
|
||||
@rem Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
@rem
|
||||
|
||||
@rem Possible environment variables:
|
||||
@rem KOTLIN_CLI_DOWNLOAD_ROOT Maven repository to download the Kotlin CLI dist from
|
||||
@rem default: https://packages.jetbrains.team/maven/p/amper/amper
|
||||
@rem KOTLIN_CLI_JRE_DOWNLOAD_ROOT Url prefix to download the Kotlin CLI JRE from.
|
||||
@rem default: https:/
|
||||
@rem KOTLIN_CLI_BOOTSTRAP_CACHE_DIR Cache directory to store the extracted JRE and Kotlin CLI distribution
|
||||
@rem KOTLIN_CLI_JAVA_HOME JRE to run the Kotlin CLI itself (optional, does not affect compilation)
|
||||
@rem KOTLIN_CLI_JAVA_OPTIONS JVM options to pass to the JVM running the Kotlin CLI (does not affect the user's application)
|
||||
@rem KOTLIN_CLI_NO_WELCOME_BANNER Disables the first-run welcome message if set to a non-empty value
|
||||
|
||||
setlocal
|
||||
|
||||
@rem The version of the Kotlin Toolchain distribution to provision and use
|
||||
set kotlin_cli_version=0.13.0-dev-4444
|
||||
@rem Establish chain of trust from here by specifying the exact checksum of the Kotlin Toolchain distribution to be run
|
||||
set kotlin_cli_sha256=13c2448146ac6dba0fa3a4e5d367dab73d8cfd7400bab348bebe905c165adecf
|
||||
|
||||
if not defined KOTLIN_CLI_DOWNLOAD_ROOT set KOTLIN_CLI_DOWNLOAD_ROOT=https://packages.jetbrains.team/maven/p/amper/amper
|
||||
if not defined KOTLIN_CLI_BOOTSTRAP_CACHE_DIR set KOTLIN_CLI_BOOTSTRAP_CACHE_DIR=%LOCALAPPDATA%\JetBrains\Kotlin\cli
|
||||
@rem remove trailing \ if present
|
||||
if [%KOTLIN_CLI_BOOTSTRAP_CACHE_DIR:~-1%] EQU [\] set KOTLIN_CLI_BOOTSTRAP_CACHE_DIR=%KOTLIN_CLI_BOOTSTRAP_CACHE_DIR:~0,-1%
|
||||
|
||||
goto :after_function_declarations
|
||||
|
||||
REM ********** Download and extract any zip or .tar.gz archive **********
|
||||
|
||||
:download_and_extract
|
||||
setlocal
|
||||
|
||||
set moniker=%~1
|
||||
set url=%~2
|
||||
set target_dir=%~3
|
||||
set sha=%~4
|
||||
set sha_size=%~5
|
||||
set show_banner_on_cache_miss=%~6
|
||||
|
||||
setlocal enableDelayedExpansion
|
||||
|
||||
set flag_file=%target_dir%\.flag
|
||||
if exist "%flag_file%" (
|
||||
set /p current_flag=<"%flag_file%"
|
||||
if "!current_flag!" == "%sha%" exit /b
|
||||
)
|
||||
|
||||
set NL=^
|
||||
|
||||
|
||||
@rem two empty lines required above for the NL character
|
||||
|
||||
@rem We have to build the welcome banner here as an env var because we
|
||||
@rem can't pass a multiline string through the single line powershell
|
||||
set welcome_banner=!NL! ^
|
||||
Welcome to !NL! ^
|
||||
!NL! ^
|
||||
@@@ @@@@ @@@ @@@ !NL! ^
|
||||
@@@ @@@@ @@@ @@@ @@@ !NL! ^
|
||||
@@@ #@@@^" ,@@@ @@@ !NL! ^
|
||||
@@@ ,@@@%% ,@@@@@@@, @@@@@@@@@@ @@@ @@@ @@@ ,@@@@@, !NL! ^
|
||||
@@@ @@@@ @@@@@%%^"%%@@@@@ @@@@@@@@@@ @@@ @@@ @@@@@@%%%%@@@@@ !NL! ^
|
||||
@@@@@@%% @@@%% %%@@@ @@@ @@@ @@@ @@@@ %%@@%% !NL! ^
|
||||
@@@ @@@@= #@@@ @@@# @@@ @@@ @@@ @@@ @@@ !NL! ^
|
||||
@@@ @@@@ #@@@ @@@# @@@ @@@ @@@ @@@ @@@ !NL! ^
|
||||
@@@ *@@@%% @@@@ @@@@ @@@ @@@ @@@ @@@ @@@ !NL! ^
|
||||
@@@ %%@@@= %%@@@@*,*@@@@%% @@@@### @@@ @@@ @@@ @@@ !NL! ^
|
||||
@@@ @@@@ ^"@@@@@@@^" %%@@@@@ @@@ @@@ @@@ @@@ !NL!
|
||||
|
||||
@rem This multiline string is actually passed as a single line to powershell, meaning #-comments are not possible.
|
||||
@rem So here are a few comments about the code below:
|
||||
@rem - we need to support both .zip and .tar.gz archives (for the Kotlin Toolchain distribution and the JRE)
|
||||
@rem - tar should be present in all Windows machines since 2018 (and usable from both cmd and powershell)
|
||||
@rem - tar requires the destination dir to exist
|
||||
@rem - We use (New-Object Net.WebClient).DownloadFile instead of Invoke-WebRequest for performance. See the issue
|
||||
@rem https://github.com/PowerShell/PowerShell/issues/16914, which is still not fixed in Windows PowerShell 5.1
|
||||
@rem - DownloadFile requires the directories in the destination file's path to exist
|
||||
set download_and_extract_ps1= ^
|
||||
Set-StrictMode -Version 3.0; ^
|
||||
$ErrorActionPreference = 'Stop'; ^
|
||||
^
|
||||
$createdNew = $false; ^
|
||||
$lock = New-Object System.Threading.Mutex($true, ('Global\kotlin-cli-bootstrap.' + '%target_dir%'.GetHashCode().ToString()), [ref]$createdNew); ^
|
||||
if (-not $createdNew) { ^
|
||||
Write-Host 'Another Kotlin CLI instance is bootstrapping. Waiting for our turn...'; ^
|
||||
[void]$lock.WaitOne(); ^
|
||||
} ^
|
||||
^
|
||||
try { ^
|
||||
if ((Get-Content '%flag_file%' -ErrorAction Ignore) -ne '%sha%') { ^
|
||||
if (('%show_banner_on_cache_miss%' -eq 'true') -and [string]::IsNullOrEmpty('%KOTLIN_CLI_NO_WELCOME_BANNER%')) { ^
|
||||
Write-Host \"$env:welcome_banner\"; ^
|
||||
Write-Host ''; ^
|
||||
Write-Host 'This is the first run of the Kotlin CLI v%kotlin_cli_version%, so we need to download the Kotlin Toolchain.'; ^
|
||||
Write-Host 'Please give us a few seconds now, subsequent runs will be faster.'; ^
|
||||
Write-Host ''; ^
|
||||
} ^
|
||||
$temp_file = '%KOTLIN_CLI_BOOTSTRAP_CACHE_DIR%\' + [System.IO.Path]::GetRandomFileName(); ^
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; ^
|
||||
Write-Host 'Downloading %moniker%...'; ^
|
||||
[void](New-Item '%KOTLIN_CLI_BOOTSTRAP_CACHE_DIR%' -ItemType Directory -Force); ^
|
||||
if (Get-Command curl.exe -errorAction SilentlyContinue) { ^
|
||||
curl.exe -L --silent --show-error --fail --output $temp_file '%url%'; ^
|
||||
} else { ^
|
||||
(New-Object Net.WebClient).DownloadFile('%url%', $temp_file); ^
|
||||
} ^
|
||||
^
|
||||
$actualSha = (Get-FileHash -Algorithm SHA%sha_size% -Path $temp_file).Hash.ToString(); ^
|
||||
if ($actualSha -ne '%sha%') { ^
|
||||
$writeErr = if ($Host.Name -eq 'ConsoleHost') { [Console]::Error.WriteLine } else { $host.ui.WriteErrorLine } ^
|
||||
$writeErr.Invoke(\"ERROR: Checksum mismatch for $temp_file (downloaded from %url%): expected checksum %sha% but got $actualSha\"); ^
|
||||
exit 1; ^
|
||||
} ^
|
||||
^
|
||||
if (Test-Path '%target_dir%') { ^
|
||||
Remove-Item '%target_dir%' -Recurse; ^
|
||||
} ^
|
||||
if ($temp_file -like '*.zip') { ^
|
||||
Add-Type -A 'System.IO.Compression.FileSystem'; ^
|
||||
[IO.Compression.ZipFile]::ExtractToDirectory($temp_file, '%target_dir%'); ^
|
||||
} else { ^
|
||||
[void](New-Item '%target_dir%' -ItemType Directory -Force); ^
|
||||
tar -xzf $temp_file -C '%target_dir%'; ^
|
||||
} ^
|
||||
Remove-Item $temp_file; ^
|
||||
^
|
||||
Set-Content '%flag_file%' -Value '%sha%'; ^
|
||||
Write-Host 'Download complete.'; ^
|
||||
Write-Host ''; ^
|
||||
} ^
|
||||
} ^
|
||||
finally { ^
|
||||
$lock.ReleaseMutex(); ^
|
||||
}
|
||||
|
||||
rem We reset the PSModulePath in case this batch script was called from PowerShell Core
|
||||
rem See https://github.com/PowerShell/PowerShell/issues/18108#issuecomment-2269703022
|
||||
set PSModulePath=
|
||||
set powershell=%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe
|
||||
"%powershell%" -NonInteractive -NoProfile -NoLogo -Command %download_and_extract_ps1%
|
||||
if errorlevel 1 exit /b 1
|
||||
exit /b 0
|
||||
|
||||
:find_project_context
|
||||
@rem Search upwards for a kotlin.bat wrapper file and/or project.yaml
|
||||
@rem Sets wrapper_script to the found wrapper path, or empty string if not found.
|
||||
@rem Returns errorlevel 0 if a valid wrapper (that is not this script itself) was found, 1 otherwise.
|
||||
set wrapper_script=
|
||||
set this_script=%~f0
|
||||
set project_dir=%CD%
|
||||
|
||||
:find_loop
|
||||
set wrapper_candidate=%project_dir%\kotlin.bat
|
||||
if "%this_script%"=="%wrapper_candidate%" (
|
||||
@rem Found itself (local wrapper case), no need to update any version or search further.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if exist "%wrapper_candidate%" (
|
||||
@rem Found a wrapper — check that a project context exists alongside it
|
||||
if exist "%project_dir%\project.yaml" (
|
||||
set wrapper_script=%wrapper_candidate%
|
||||
exit /b 0
|
||||
)
|
||||
if exist "%project_dir%\module.yaml" (
|
||||
set wrapper_script=%wrapper_candidate%
|
||||
exit /b 0
|
||||
)
|
||||
echo WARNING: Found wrapper script '%wrapper_candidate%', but no project.yaml or module.yaml near it. Skipping. >&2
|
||||
@rem Continue the search
|
||||
goto :find_next_parent
|
||||
)
|
||||
|
||||
if exist "%project_dir%\project.yaml" (
|
||||
@rem Found project.yaml but no wrapper alongside it
|
||||
echo WARNING: Found a project.yaml in '%project_dir%', but the wrapper script is missing; using Kotlin Toolchain v%kotlin_cli_version%. >&2
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:find_next_parent
|
||||
@rem Move to parent directory
|
||||
for %%P in ("%project_dir%\..") do set parent_dir=%%~fP
|
||||
if "%parent_dir%"=="%project_dir%" (
|
||||
@rem Reached the root, stop searching
|
||||
exit /b 1
|
||||
)
|
||||
set project_dir=%parent_dir%
|
||||
goto :find_loop
|
||||
|
||||
:parse_project_context
|
||||
@rem Parse kotlin_cli_version and kotlin_cli_sha256 from the found wrapper_script without executing it.
|
||||
set parsed_kotlin_cli_version=
|
||||
set parsed_kotlin_cli_sha256=
|
||||
|
||||
for /f "tokens=2 delims==" %%A in ('findstr /r /c:"^set kotlin_cli_version=[A-Za-z0-9._+-]*$" "%wrapper_script%"') do (
|
||||
if not defined parsed_kotlin_cli_version set parsed_kotlin_cli_version=%%A
|
||||
)
|
||||
for /f "tokens=2 delims==" %%A in ('findstr /r /c:"^set kotlin_cli_sha256=[0-9a-fA-F]*$" "%wrapper_script%"') do (
|
||||
if not defined parsed_kotlin_cli_sha256 set parsed_kotlin_cli_sha256=%%A
|
||||
)
|
||||
|
||||
if not defined parsed_kotlin_cli_version (
|
||||
echo ERROR: Suspicious local wrapper script: failed to detect the distribution version in '%wrapper_script%' >&2
|
||||
exit /b 1
|
||||
)
|
||||
if not defined parsed_kotlin_cli_sha256 (
|
||||
echo ERROR: Suspicious local wrapper script: failed to detect the distribution checksum in '%wrapper_script%' >&2
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
@rem Overwrite builtin values and proceed
|
||||
set kotlin_cli_version=%parsed_kotlin_cli_version%
|
||||
set kotlin_cli_sha256=%parsed_kotlin_cli_sha256%
|
||||
exit /b 0
|
||||
|
||||
:fail
|
||||
echo ERROR: Kotlin CLI bootstrap failed, see errors above
|
||||
exit /b 1
|
||||
|
||||
:after_function_declarations
|
||||
|
||||
REM ********** Project-local version detection **********
|
||||
|
||||
if defined KOTLIN_CLI_WRAPPER_ALWAYS_USE_INTRINSIC_VERSION goto :after_local_version_detection
|
||||
|
||||
call :find_project_context
|
||||
if errorlevel 1 goto :after_local_version_detection
|
||||
call :parse_project_context
|
||||
if errorlevel 1 goto fail
|
||||
:after_local_version_detection
|
||||
|
||||
REM ********** Provision the Kotlin Toolchain distribution **********
|
||||
|
||||
set kotlin_cli_url=%KOTLIN_CLI_DOWNLOAD_ROOT%/org/jetbrains/kotlin/kotlin-cli/%kotlin_cli_version%/kotlin-cli-%kotlin_cli_version%-dist.tgz
|
||||
set kotlin_cli_target_dir=%KOTLIN_CLI_BOOTSTRAP_CACHE_DIR%\kotlin-cli-%kotlin_cli_version%
|
||||
call :download_and_extract "Kotlin Toolchain distribution v%kotlin_cli_version%" "%kotlin_cli_url%" "%kotlin_cli_target_dir%" "%kotlin_cli_sha256%" "256" "true"
|
||||
if errorlevel 1 goto fail
|
||||
|
||||
REM ********** Launch the Kotlin CLI **********
|
||||
|
||||
rem Determine the correct busybox binary based on architecture
|
||||
if "%PROCESSOR_ARCHITECTURE%"=="ARM64" (
|
||||
set busybox_exe=%kotlin_cli_target_dir%\bin\busybox64a.exe
|
||||
) else if "%PROCESSOR_ARCHITECTURE%"=="AMD64" (
|
||||
set busybox_exe=%kotlin_cli_target_dir%\bin\busybox64u.exe
|
||||
) else (
|
||||
echo Unsupported architecture %PROCESSOR_ARCHITECTURE% >&2
|
||||
goto fail
|
||||
)
|
||||
|
||||
rem We use busybox here because it doesn't reinterpret the user-passed command-line arguments (that we pass via %*).
|
||||
rem Also this way we can use the unified launcher script (.sh)
|
||||
set KOTLIN_CLI_WRAPPER_PATH=%~f0
|
||||
|
||||
rem The '& call' after the app run is to avoid the "Terminate batch job (Y/N)?" prompt
|
||||
"%busybox_exe%" sh "%kotlin_cli_target_dir%\bin\launcher.sh" %* & call :exitWithErrorLevel
|
||||
|
||||
:exitWithErrorLevel
|
||||
@rem We use "%COMSPEC%" /d /c exit so that and/or operators work properly when calling kotlin.bat directly from a script
|
||||
@rem without the `call` command. With a plain `exit /B %ERRORLEVEL%`, using `kotlin.bat && echo success` would print
|
||||
@rem 'success' even in case of failure. Consumers would have to use `call kotlin.bat && echo success` for it to work.`
|
||||
"%COMSPEC%" /d /c exit %ERRORLEVEL%
|
||||
@@ -1,119 +0,0 @@
|
||||
product:
|
||||
type: kmp/lib
|
||||
platforms:
|
||||
- android
|
||||
- js
|
||||
- jvm
|
||||
- linuxArm64
|
||||
- linuxX64
|
||||
- mingwX64
|
||||
- wasmJs
|
||||
|
||||
description: Simple logging library for kotlin
|
||||
|
||||
repositories:
|
||||
- id: mavenLocal
|
||||
url: mavenLocal
|
||||
resolve: false
|
||||
publish: true
|
||||
|
||||
plugins:
|
||||
dokka:
|
||||
enabled: true
|
||||
moduleName: KSLog
|
||||
moduleVersion: 2.1.0
|
||||
sourceLinkBaseUrl: https://github.com/InsanusMokrassar/KSLog/blob/master/kslog
|
||||
skipDeprecated: true
|
||||
github-release:
|
||||
enabled: true
|
||||
owner: InsanusMokrassar
|
||||
repository: KSLog
|
||||
version: 2.1.0
|
||||
tagPrefix: v
|
||||
targetCommitish: 2.1.0
|
||||
changelogFile: CHANGELOG.md
|
||||
|
||||
settings:
|
||||
android:
|
||||
buildToolsVersion: 37.0.0
|
||||
compileSdk: 37
|
||||
minSdk: 21
|
||||
namespace: dev.inmo.kslog
|
||||
junit: junit-4
|
||||
jvm:
|
||||
jdk:
|
||||
version: 17
|
||||
release: null
|
||||
kotlin:
|
||||
version: 2.4.20
|
||||
publishing:
|
||||
enabled: true
|
||||
group: dev.inmo
|
||||
version: 2.1.0
|
||||
artifactId: kslog
|
||||
signArtifacts: true
|
||||
publishSources: true
|
||||
pom:
|
||||
name: kslog
|
||||
description: Simple logging library for kotlin
|
||||
url: https://github.com/InsanusMokrassar/KSLog
|
||||
scm:
|
||||
url: https://github.com/InsanusMokrassar/KSLog.git
|
||||
developerConnection: scm:git:[fetch=]https://github.com/InsanusMokrassar/KSLog.git[push=]https://github.com/InsanusMokrassar/KSLog.git
|
||||
developers:
|
||||
- id: Akkihi
|
||||
name: Akkihi
|
||||
email: akkihiguy@gmail.com
|
||||
- id: InsanusMokrassar
|
||||
name: InsanusMokrassar
|
||||
email: ovsyannikov.alexey95@gmail.com
|
||||
licenses:
|
||||
- name: MIT License
|
||||
url: https://opensource.org/licenses/MIT
|
||||
|
||||
settings@android:
|
||||
kotlin:
|
||||
freeCompilerArgs:
|
||||
- -module-name=dev.inmo_kslog
|
||||
- -jvm-target=17
|
||||
|
||||
settings@js:
|
||||
kotlin:
|
||||
freeCompilerArgs:
|
||||
- -Xir-module-name=dev.inmo:kslog
|
||||
|
||||
settings@jvm:
|
||||
kotlin:
|
||||
freeCompilerArgs:
|
||||
- -module-name=dev.inmo_kslog
|
||||
- -jvm-target=1.8
|
||||
|
||||
settings@linuxArm64:
|
||||
kotlin:
|
||||
freeCompilerArgs:
|
||||
- -module-name=dev.inmo:kslog
|
||||
- -Xshort-module-name=kslog
|
||||
|
||||
settings@linuxX64:
|
||||
kotlin:
|
||||
freeCompilerArgs:
|
||||
- -module-name=dev.inmo:kslog
|
||||
- -Xshort-module-name=kslog
|
||||
|
||||
settings@mingwX64:
|
||||
kotlin:
|
||||
freeCompilerArgs:
|
||||
- -module-name=dev.inmo:kslog
|
||||
- -Xshort-module-name=kslog
|
||||
|
||||
settings@native:
|
||||
kotlin:
|
||||
freeCompilerArgs:
|
||||
- -module-name=dev.inmo:kslog_nativeMain
|
||||
- -Xshort-module-name=kslog_nativeMain
|
||||
- -Xexport-kdoc
|
||||
|
||||
settings@wasmJs:
|
||||
kotlin:
|
||||
freeCompilerArgs:
|
||||
- -Xir-module-name=dev.inmo:kslog
|
||||
@@ -1,57 +0,0 @@
|
||||
package dev.inmo.kslog.common
|
||||
|
||||
import dev.inmo.kslog.common.filter.filtered
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertSame
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class CallbackKSLogTest {
|
||||
@Test
|
||||
fun forwardsExactArguments() {
|
||||
val expectedThrowable = IllegalStateException("boom")
|
||||
var observedLevel: LogLevel? = null
|
||||
var observedTag: String? = null
|
||||
var observedMessage: Any? = null
|
||||
var observedThrowable: Throwable? = null
|
||||
|
||||
val logger = KSLog { level, tag, message, throwable ->
|
||||
observedLevel = level
|
||||
observedTag = tag
|
||||
observedMessage = message
|
||||
observedThrowable = throwable
|
||||
}
|
||||
|
||||
logger.performLog(LogLevel.WARNING, "network", "retrying", expectedThrowable)
|
||||
|
||||
assertEquals(LogLevel.WARNING, observedLevel)
|
||||
assertEquals("network", observedTag)
|
||||
assertEquals("retrying", observedMessage)
|
||||
assertSame(expectedThrowable, observedThrowable)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun filteredLoggerDoesNotBuildRejectedLazyMessage() {
|
||||
var callbackInvoked = false
|
||||
var messageBuilt = false
|
||||
val logger = KSLog { _, _, _, _ -> callbackInvoked = true }
|
||||
.filtered { level, _, _ -> level >= LogLevel.INFO }
|
||||
|
||||
logger.performLog(LogLevel.DEBUG, "test", null) {
|
||||
messageBuilt = true
|
||||
"expensive"
|
||||
}
|
||||
|
||||
assertFalse(callbackInvoked)
|
||||
assertFalse(messageBuilt)
|
||||
|
||||
logger.performLog(LogLevel.ERROR, "test", null) {
|
||||
messageBuilt = true
|
||||
"needed"
|
||||
}
|
||||
|
||||
assertTrue(callbackInvoked)
|
||||
assertTrue(messageBuilt)
|
||||
}
|
||||
}
|
||||
@@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2020-2021 JetBrains s.r.o. and and respective authors and developers.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,27 +0,0 @@
|
||||
# KSLog Dokka Toolchain plugin
|
||||
|
||||
This local plugin is adapted from JetBrains Kotlin Toolchain's
|
||||
`build-sources/dokka` plugin at release `v0.13.0`, commit
|
||||
`abc7a5f7579e9ef267107782048c76777f0bf932`.
|
||||
|
||||
Upstream source: <https://github.com/JetBrains/kotlin-toolchain/tree/abc7a5f7579e9ef267107782048c76777f0bf932/build-sources/dokka>
|
||||
|
||||
Changes made for KSLog:
|
||||
|
||||
- models the KMP common/JVM/Android/JS/WasmJS/native source-set graph;
|
||||
- uses Dokka 2.2.0 to match the pre-migration build;
|
||||
- provides separate `dokkaHtml` and `dokkaGfm` commands;
|
||||
- writes to the preserved `build/dokka/html` and `build/dokka/gfm` paths;
|
||||
- preserves deprecated-member filtering and GitHub source links;
|
||||
- supplies the resolved JVM classpath plus transformed Common and matching
|
||||
JS/WasmJS standard-library KLIBs materialized by the documented prebuild;
|
||||
- fails generation if Dokka emits an unresolved-type placeholder, preventing
|
||||
the deployment workflow from uploading incomplete API signatures.
|
||||
|
||||
The legacy Gradle `DOKKA_PATH` override remains Gradle-only. The Toolchain
|
||||
plugin intentionally accepts only output directories below `build/dokka`,
|
||||
rejects symbolic-link ancestors, and verifies real-path containment before
|
||||
cleaning generated output.
|
||||
|
||||
The adapted Kotlin files retain the upstream Apache-2.0 header. See
|
||||
`LICENSE.txt` in this directory.
|
||||
@@ -1,20 +0,0 @@
|
||||
product: jvm/amper-plugin
|
||||
|
||||
description: KMP-capable Dokka HTML and GFM tasks adapted from the Kotlin Toolchain Dokka plugin.
|
||||
|
||||
pluginInfo:
|
||||
id: dokka
|
||||
settingsClass: dev.inmo.kslog.build.dokka.DokkaSettings
|
||||
|
||||
test-dependencies:
|
||||
- $libs.kotlin.test.junit
|
||||
|
||||
settings:
|
||||
junit: junit-4
|
||||
jvm:
|
||||
jdk:
|
||||
version: 17
|
||||
release: 17
|
||||
kotlin:
|
||||
version: 2.4.20
|
||||
serialization: json
|
||||
@@ -1,37 +0,0 @@
|
||||
tasks:
|
||||
html:
|
||||
action: !dev.inmo.kslog.build.dokka.dokkaHtml
|
||||
projectRootDir: ${project.rootDir}
|
||||
moduleRootDir: ${module.rootDir}
|
||||
moduleCompileClasspath: ${module.compileClasspath}
|
||||
runtimeClasspath:
|
||||
- $libs.dokka.cli
|
||||
pluginsClasspath:
|
||||
- $libs.dokka.base
|
||||
- $libs.dokka.analysis.kotlin.descriptors
|
||||
- $libs.kotlinx.html.jvm
|
||||
- $libs.freemarker
|
||||
outputDir: ${project.rootDir}/build/dokka/html
|
||||
settings: ${pluginSettings}
|
||||
|
||||
gfm:
|
||||
action: !dev.inmo.kslog.build.dokka.dokkaGfm
|
||||
projectRootDir: ${project.rootDir}
|
||||
moduleRootDir: ${module.rootDir}
|
||||
moduleCompileClasspath: ${module.compileClasspath}
|
||||
runtimeClasspath:
|
||||
- $libs.dokka.cli
|
||||
pluginsClasspath:
|
||||
- $libs.dokka.base
|
||||
- $libs.dokka.analysis.kotlin.descriptors
|
||||
- $libs.dokka.gfm
|
||||
- $libs.kotlinx.html.jvm
|
||||
- $libs.freemarker
|
||||
outputDir: ${project.rootDir}/build/dokka/gfm
|
||||
settings: ${pluginSettings}
|
||||
|
||||
commands:
|
||||
- name: dokkaHtml
|
||||
performedBy: html
|
||||
- name: dokkaGfm
|
||||
performedBy: gfm
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
*/
|
||||
|
||||
/*
|
||||
* KSLog modification notice: adapted from JetBrains Kotlin Toolchain v0.13.0
|
||||
* build-sources/dokka. KSLog extends the configuration model for its KMP
|
||||
* source-set graph and HTML/GFM plugin configuration. See ../README.md.
|
||||
*/
|
||||
|
||||
package dev.inmo.kslog.build.dokka
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal data class DokkaConfiguration(
|
||||
val moduleName: String,
|
||||
val moduleVersion: String,
|
||||
val outputDir: String,
|
||||
val sourceSets: List<DokkaSourceSet>,
|
||||
val pluginsClasspath: List<String>,
|
||||
val pluginsConfiguration: List<PluginConfiguration>,
|
||||
val failOnWarning: Boolean = false,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
internal data class SourceSetId(
|
||||
val scopeId: String,
|
||||
val sourceSetName: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
internal data class DokkaSourceSet(
|
||||
val sourceSetID: SourceSetId,
|
||||
val displayName: String,
|
||||
val dependentSourceSets: List<SourceSetId>,
|
||||
val classpath: List<String>,
|
||||
val sourceRoots: List<String>,
|
||||
val documentedVisibilities: List<String> = listOf("PUBLIC"),
|
||||
val skipDeprecated: Boolean,
|
||||
val jdkVersion: Int,
|
||||
val languageVersion: String = "2.4",
|
||||
val apiVersion: String = "2.4",
|
||||
val analysisPlatform: String,
|
||||
val sourceLinks: List<SourceLink>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
internal data class SourceLink(
|
||||
val localDirectory: String,
|
||||
val remoteUrl: String,
|
||||
val remoteLineSuffix: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
internal data class PluginConfiguration(
|
||||
val fqPluginName: String,
|
||||
val serializationFormat: String,
|
||||
val values: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
internal data class DokkaBase(
|
||||
val footerMessage: String? = null,
|
||||
)
|
||||
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
*/
|
||||
|
||||
/*
|
||||
* KSLog modification notice: adapted from JetBrains Kotlin Toolchain v0.13.0
|
||||
* build-sources/dokka. KSLog adds versioned KMP documentation settings and
|
||||
* project-specific source-link defaults. See ../README.md for provenance.
|
||||
*/
|
||||
|
||||
package dev.inmo.kslog.build.dokka
|
||||
|
||||
import org.jetbrains.amper.plugins.Configurable
|
||||
|
||||
@Configurable
|
||||
interface DokkaSettings {
|
||||
val moduleName: String get() = "KSLog"
|
||||
val moduleVersion: String
|
||||
val sourceLinkBaseUrl: String get() = "https://github.com/InsanusMokrassar/KSLog/blob/master/"
|
||||
val sourceLinkLineSuffix: String get() = "#L"
|
||||
val skipDeprecated: Boolean get() = true
|
||||
}
|
||||
@@ -1,340 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
*/
|
||||
|
||||
/*
|
||||
* KSLog modification notice: adapted from JetBrains Kotlin Toolchain v0.13.0
|
||||
* build-sources/dokka. KSLog adds KMP source sets, HTML/GFM entry points, and
|
||||
* project-confined, symlink-safe generated-output cleanup. See ../README.md.
|
||||
*/
|
||||
|
||||
package dev.inmo.kslog.build.dokka
|
||||
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.jetbrains.amper.plugins.Classpath
|
||||
import org.jetbrains.amper.plugins.ExecutionAvoidance
|
||||
import org.jetbrains.amper.plugins.Input
|
||||
import org.jetbrains.amper.plugins.Output
|
||||
import org.jetbrains.amper.plugins.TaskAction
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.LinkOption
|
||||
import java.nio.file.Path
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.io.path.ExperimentalPathApi
|
||||
import kotlin.io.path.createDirectories
|
||||
import kotlin.io.path.deleteIfExists
|
||||
import kotlin.io.path.deleteRecursively
|
||||
import kotlin.io.path.exists
|
||||
import kotlin.io.path.pathString
|
||||
import kotlin.io.path.writeText
|
||||
|
||||
@TaskAction(ExecutionAvoidance.Disabled)
|
||||
fun dokkaHtml(
|
||||
projectRootDir: String,
|
||||
moduleRootDir: String,
|
||||
@Input moduleCompileClasspath: Classpath,
|
||||
@Input runtimeClasspath: Classpath,
|
||||
@Input pluginsClasspath: Classpath,
|
||||
@Output outputDir: Path,
|
||||
settings: DokkaSettings,
|
||||
) = runDokka(
|
||||
projectRootDir,
|
||||
moduleRootDir,
|
||||
moduleCompileClasspath,
|
||||
runtimeClasspath,
|
||||
pluginsClasspath,
|
||||
outputDir,
|
||||
settings,
|
||||
)
|
||||
|
||||
@TaskAction(ExecutionAvoidance.Disabled)
|
||||
fun dokkaGfm(
|
||||
projectRootDir: String,
|
||||
moduleRootDir: String,
|
||||
@Input moduleCompileClasspath: Classpath,
|
||||
@Input runtimeClasspath: Classpath,
|
||||
@Input pluginsClasspath: Classpath,
|
||||
@Output outputDir: Path,
|
||||
settings: DokkaSettings,
|
||||
) = runDokka(
|
||||
projectRootDir,
|
||||
moduleRootDir,
|
||||
moduleCompileClasspath,
|
||||
runtimeClasspath,
|
||||
pluginsClasspath,
|
||||
outputDir,
|
||||
settings,
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalPathApi::class)
|
||||
private fun runDokka(
|
||||
projectRootDir: String,
|
||||
moduleRootDir: String,
|
||||
moduleCompileClasspath: Classpath,
|
||||
runtimeClasspath: Classpath,
|
||||
pluginsClasspath: Classpath,
|
||||
outputDir: Path,
|
||||
settings: DokkaSettings,
|
||||
) {
|
||||
val normalizedRoot = Path.of(moduleRootDir).toRealPath()
|
||||
val normalizedOutput = prepareDokkaOutput(projectRootDir, outputDir)
|
||||
|
||||
val commonId = SourceSetId(settings.moduleName, "commonMain")
|
||||
val sourceLink = SourceLink(
|
||||
localDirectory = normalizedRoot.pathString,
|
||||
remoteUrl = settings.sourceLinkBaseUrl,
|
||||
remoteLineSuffix = settings.sourceLinkLineSuffix,
|
||||
)
|
||||
val analysisClasspaths = resolveDokkaAnalysisClasspaths(moduleCompileClasspath.resolvedFiles)
|
||||
|
||||
fun sourceSet(
|
||||
directoryName: String,
|
||||
sourceSetName: String,
|
||||
displayName: String,
|
||||
analysisPlatform: String,
|
||||
dependentOnCommon: Boolean,
|
||||
classpath: List<String> = emptyList(),
|
||||
): DokkaSourceSet? {
|
||||
val sourceRoot = normalizedRoot.resolve(directoryName)
|
||||
if (!sourceRoot.exists()) return null
|
||||
return DokkaSourceSet(
|
||||
sourceSetID = SourceSetId(settings.moduleName, sourceSetName),
|
||||
displayName = displayName,
|
||||
dependentSourceSets = if (dependentOnCommon) listOf(commonId) else emptyList(),
|
||||
classpath = classpath,
|
||||
sourceRoots = listOf(sourceRoot.pathString),
|
||||
skipDeprecated = settings.skipDeprecated,
|
||||
jdkVersion = if (displayName == "JVM") 8 else 17,
|
||||
analysisPlatform = analysisPlatform,
|
||||
sourceLinks = listOf(sourceLink),
|
||||
)
|
||||
}
|
||||
|
||||
val androidClasspath = buildList {
|
||||
addAll(analysisClasspaths.jvm)
|
||||
val sdkRoot = System.getenv("ANDROID_HOME") ?: System.getenv("ANDROID_SDK_ROOT")
|
||||
if (sdkRoot != null) {
|
||||
val androidJar = listOf("android-37.0", "android-37")
|
||||
.map { Path.of(sdkRoot).resolve("platforms/$it/android.jar") }
|
||||
.firstOrNull { Files.isRegularFile(it) }
|
||||
if (androidJar != null) add(androidJar.pathString)
|
||||
}
|
||||
}
|
||||
|
||||
val sourceSets = listOfNotNull(
|
||||
sourceSet(
|
||||
"src",
|
||||
"commonMain",
|
||||
"Common",
|
||||
"common",
|
||||
dependentOnCommon = false,
|
||||
classpath = analysisClasspaths.commonNative,
|
||||
),
|
||||
sourceSet(
|
||||
"src@jvm",
|
||||
"jvmMain",
|
||||
"JVM",
|
||||
"jvm",
|
||||
dependentOnCommon = true,
|
||||
classpath = analysisClasspaths.jvm,
|
||||
),
|
||||
sourceSet("src@android", "androidMain", "Android", "jvm", dependentOnCommon = true, classpath = androidClasspath),
|
||||
sourceSet("src@js", "jsMain", "JS", "js", dependentOnCommon = true, classpath = analysisClasspaths.js),
|
||||
sourceSet(
|
||||
"src@wasmJs",
|
||||
"wasmJsMain",
|
||||
"WasmJS",
|
||||
"js",
|
||||
dependentOnCommon = true,
|
||||
classpath = analysisClasspaths.wasmJs,
|
||||
),
|
||||
sourceSet(
|
||||
"src@native",
|
||||
"nativeMain",
|
||||
"Native",
|
||||
"native",
|
||||
dependentOnCommon = true,
|
||||
classpath = analysisClasspaths.commonNative,
|
||||
),
|
||||
)
|
||||
|
||||
val configuration = DokkaConfiguration(
|
||||
moduleName = settings.moduleName,
|
||||
moduleVersion = settings.moduleVersion,
|
||||
outputDir = normalizedOutput.pathString,
|
||||
sourceSets = sourceSets,
|
||||
pluginsClasspath = pluginsClasspath.resolvedFiles.map { it.pathString },
|
||||
pluginsConfiguration = listOf(
|
||||
PluginConfiguration(
|
||||
fqPluginName = "org.jetbrains.dokka.base.DokkaBase",
|
||||
serializationFormat = "JSON",
|
||||
values = Json.encodeToString(DokkaBase(footerMessage = "KSLog ${settings.moduleVersion}")),
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
val configurationFile = Files.createTempFile("kslog-dokka-", ".json")
|
||||
try {
|
||||
configurationFile.writeText(Json.encodeToString(configuration))
|
||||
val cliJar = runtimeClasspath.resolvedFiles.single()
|
||||
val commandLine = listOf(
|
||||
ProcessHandle.current().info().command().orElse("java"),
|
||||
"-jar",
|
||||
cliJar.pathString,
|
||||
configurationFile.pathString,
|
||||
)
|
||||
val process = ProcessBuilder(commandLine)
|
||||
.directory(normalizedRoot.toFile())
|
||||
.start()
|
||||
val capture = listOf(
|
||||
thread { process.inputStream.copyTo(System.out) },
|
||||
thread { process.errorStream.copyTo(System.err) },
|
||||
)
|
||||
val exitCode = process.waitFor()
|
||||
capture.forEach { it.join() }
|
||||
check(exitCode == 0) { "Dokka terminated with exit code $exitCode" }
|
||||
requireNoUnresolvedTypes(normalizedOutput)
|
||||
} finally {
|
||||
configurationFile.deleteIfExists()
|
||||
}
|
||||
}
|
||||
|
||||
internal data class DokkaAnalysisClasspaths(
|
||||
val commonNative: List<String>,
|
||||
val jvm: List<String>,
|
||||
val js: List<String>,
|
||||
val wasmJs: List<String>,
|
||||
)
|
||||
|
||||
internal fun resolveDokkaAnalysisClasspaths(moduleClasspath: List<Path>): DokkaAnalysisClasspaths {
|
||||
val kotlinStdlib = checkNotNull(
|
||||
moduleClasspath.firstOrNull { path ->
|
||||
val version = path.parent?.fileName?.toString()
|
||||
version != null &&
|
||||
path.parent?.parent?.fileName?.toString() == "kotlin-stdlib" &&
|
||||
path.fileName?.toString() == "kotlin-stdlib-$version.jar"
|
||||
}
|
||||
) {
|
||||
"Cannot locate the Kotlin JVM stdlib in the module compile classpath; " +
|
||||
"it is required to discover the matching JS and WasmJS analysis KLIBs"
|
||||
}
|
||||
val versionDirectory = checkNotNull(kotlinStdlib.parent)
|
||||
val version = versionDirectory.fileName.toString()
|
||||
val kotlinGroupDirectory = checkNotNull(versionDirectory.parent?.parent)
|
||||
val mavenCacheDirectory = checkNotNull(kotlinGroupDirectory.parent?.parent?.parent)
|
||||
check(mavenCacheDirectory.fileName.toString() == ".m2.cache") {
|
||||
"Unexpected Kotlin Toolchain Maven cache layout for $kotlinStdlib"
|
||||
}
|
||||
val transformedStdlibDirectory = checkNotNull(mavenCacheDirectory.parent)
|
||||
.resolve("kotlin/kotlinTransformedMetadataLibraries/org.jetbrains.kotlin/kotlin-stdlib/$version")
|
||||
val commonStdlib = if (Files.isDirectory(transformedStdlibDirectory)) {
|
||||
Files.walk(transformedStdlibDirectory, 2).use { paths ->
|
||||
paths
|
||||
.filter { Files.isRegularFile(it) }
|
||||
.filter { it.fileName.toString() == "kotlin-stdlib-commonMain-$version.klib" }
|
||||
.sorted()
|
||||
.findFirst()
|
||||
.orElse(null)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
fun platformStdlib(artifactId: String): Path = kotlinGroupDirectory
|
||||
.resolve(artifactId)
|
||||
.resolve(version)
|
||||
.resolve("$artifactId-$version.klib")
|
||||
|
||||
val jsStdlib = platformStdlib("kotlin-stdlib-js")
|
||||
val wasmJsStdlib = platformStdlib("kotlin-stdlib-wasm-js")
|
||||
val missing = listOfNotNull(jsStdlib, wasmJsStdlib).filterNot(Files::isRegularFile)
|
||||
check(commonStdlib != null && missing.isEmpty()) {
|
||||
"Missing Kotlin analysis KLIB(s) for Dokka below $transformedStdlibDirectory or in " +
|
||||
"${missing.joinToString()}. " +
|
||||
"Run `./kotlin build -m kslog -p js -p wasmJs` before documentation generation."
|
||||
}
|
||||
|
||||
return DokkaAnalysisClasspaths(
|
||||
commonNative = listOf(commonStdlib.pathString),
|
||||
jvm = moduleClasspath.map { it.pathString },
|
||||
js = listOf(jsStdlib.pathString),
|
||||
wasmJs = listOf(wasmJsStdlib.pathString),
|
||||
)
|
||||
}
|
||||
|
||||
internal fun requireNoUnresolvedTypes(outputDir: Path) {
|
||||
val unresolvedFiles = Files.walk(outputDir).use { paths ->
|
||||
paths
|
||||
.filter { Files.isRegularFile(it) }
|
||||
.filter { path ->
|
||||
path.fileName.toString().substringAfterLast('.', "") in setOf("html", "json", "md")
|
||||
}
|
||||
.filter { Files.readString(it).contains("Error class: unknown class") }
|
||||
.map { outputDir.relativize(it).pathString }
|
||||
.sorted()
|
||||
.toList()
|
||||
}
|
||||
check(unresolvedFiles.isEmpty()) {
|
||||
"Dokka generated unresolved type placeholders in ${unresolvedFiles.size} file(s): " +
|
||||
unresolvedFiles.joinToString(limit = 10)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalPathApi::class)
|
||||
internal fun prepareDokkaOutput(projectRootDir: String, outputDir: Path): Path {
|
||||
val configuredProjectRoot = Path.of(projectRootDir).toAbsolutePath().normalize()
|
||||
require(Files.isDirectory(configuredProjectRoot, LinkOption.NOFOLLOW_LINKS)) {
|
||||
"Project root must be a real directory, not a symbolic link: $configuredProjectRoot"
|
||||
}
|
||||
|
||||
val configuredGeneratedRoot = configuredProjectRoot.resolve("build/dokka").normalize()
|
||||
val configuredOutput = outputDir.toAbsolutePath().normalize()
|
||||
require(configuredOutput.parent == configuredGeneratedRoot) {
|
||||
"Refusing to clean Dokka output outside a direct child of $configuredGeneratedRoot: $configuredOutput"
|
||||
}
|
||||
|
||||
val realProjectRoot = configuredProjectRoot.toRealPath()
|
||||
val relativeOutput = configuredProjectRoot.relativize(configuredOutput)
|
||||
val generatedRoot = realProjectRoot.resolve("build/dokka").normalize()
|
||||
val output = realProjectRoot.resolve(relativeOutput).normalize()
|
||||
require(output.parent == generatedRoot) {
|
||||
"Resolved Dokka output escaped $generatedRoot: $output"
|
||||
}
|
||||
|
||||
requireNoSymbolicLinkAncestors(realProjectRoot, output)
|
||||
Files.createDirectories(generatedRoot)
|
||||
requireNoSymbolicLinkAncestors(realProjectRoot, output)
|
||||
|
||||
val realGeneratedRoot = generatedRoot.toRealPath()
|
||||
require(realGeneratedRoot.startsWith(realProjectRoot)) {
|
||||
"Resolved Dokka root escaped the project: $realGeneratedRoot"
|
||||
}
|
||||
|
||||
if (Files.exists(output, LinkOption.NOFOLLOW_LINKS)) {
|
||||
requireNoSymbolicLinkAncestors(realProjectRoot, output)
|
||||
val realOutput = output.toRealPath()
|
||||
require(realOutput.parent == realGeneratedRoot) {
|
||||
"Resolved Dokka output escaped $realGeneratedRoot: $realOutput"
|
||||
}
|
||||
output.deleteRecursively()
|
||||
}
|
||||
|
||||
output.createDirectories()
|
||||
requireNoSymbolicLinkAncestors(realProjectRoot, output)
|
||||
val createdOutput = output.toRealPath()
|
||||
require(createdOutput.parent == realGeneratedRoot) {
|
||||
"Created Dokka output escaped $realGeneratedRoot: $createdOutput"
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
private fun requireNoSymbolicLinkAncestors(root: Path, target: Path) {
|
||||
require(target.startsWith(root)) { "Path is outside the project root: $target" }
|
||||
var current = root
|
||||
for (component in root.relativize(target)) {
|
||||
current = current.resolve(component)
|
||||
require(!Files.isSymbolicLink(current)) {
|
||||
"Refusing Dokka cleanup through symbolic link: $current"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
package dev.inmo.kslog.build.dokka
|
||||
|
||||
import java.nio.file.Files
|
||||
import kotlin.io.path.ExperimentalPathApi
|
||||
import kotlin.io.path.createDirectories
|
||||
import kotlin.io.path.deleteRecursively
|
||||
import kotlin.io.path.exists
|
||||
import kotlin.io.path.readText
|
||||
import kotlin.io.path.writeText
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
@OptIn(ExperimentalPathApi::class)
|
||||
class RunDokkaOutputGuardTest {
|
||||
@Test
|
||||
fun rejectsSymlinkedDokkaAncestorWithoutTouchingOutsideFiles() {
|
||||
val projectRoot = Files.createTempDirectory("kslog-dokka-project-")
|
||||
val outside = Files.createTempDirectory("kslog-dokka-outside-")
|
||||
val sentinel = outside.resolve("sentinel.txt")
|
||||
sentinel.writeText("preserve me")
|
||||
val build = projectRoot.resolve("build").createDirectories()
|
||||
val dokkaLink = build.resolve("dokka")
|
||||
Files.createSymbolicLink(dokkaLink, outside)
|
||||
|
||||
try {
|
||||
val failure = assertFailsWith<IllegalArgumentException> {
|
||||
prepareDokkaOutput(projectRoot.toString(), dokkaLink.resolve("html"))
|
||||
}
|
||||
assertTrue(failure.message.orEmpty().contains("symbolic link"))
|
||||
assertEquals("preserve me", sentinel.readText())
|
||||
} finally {
|
||||
Files.deleteIfExists(dokkaLink)
|
||||
projectRoot.deleteRecursively()
|
||||
outside.deleteRecursively()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cleansOnlyTheSelectedGeneratedOutputDirectory() {
|
||||
val projectRoot = Files.createTempDirectory("kslog-dokka-project-")
|
||||
val html = projectRoot.resolve("build/dokka/html").createDirectories()
|
||||
val stale = html.resolve("stale.txt")
|
||||
stale.writeText("old")
|
||||
val gfmSentinel = projectRoot.resolve("build/dokka/gfm/sentinel.txt")
|
||||
gfmSentinel.parent.createDirectories()
|
||||
gfmSentinel.writeText("keep")
|
||||
|
||||
try {
|
||||
val prepared = prepareDokkaOutput(projectRoot.toString(), html)
|
||||
assertEquals(html.toRealPath(), prepared.toRealPath())
|
||||
assertTrue(prepared.exists())
|
||||
assertFalse(stale.exists())
|
||||
assertEquals("keep", gfmSentinel.readText())
|
||||
} finally {
|
||||
projectRoot.deleteRecursively()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun rejectsGeneratedDocumentationWithUnresolvedTypePlaceholders() {
|
||||
val output = Files.createTempDirectory("kslog-dokka-output-")
|
||||
val generatedPage = output.resolve("broken.md")
|
||||
generatedPage.writeText("fun example(): <Error class: unknown class>")
|
||||
|
||||
try {
|
||||
val failure = assertFailsWith<IllegalStateException> {
|
||||
requireNoUnresolvedTypes(output)
|
||||
}
|
||||
assertTrue(failure.message.orEmpty().contains("broken.md"))
|
||||
} finally {
|
||||
output.deleteRecursively()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun resolvesMatchingCommonJsAndWasmAnalysisKlibsFromTheToolchainCache() {
|
||||
val cache = Files.createTempDirectory("kslog-dokka-cache-")
|
||||
val kotlinGroup = cache.resolve(".m2.cache/org/jetbrains/kotlin")
|
||||
val version = "2.4.20"
|
||||
val kotlinStdlib = kotlinGroup
|
||||
.resolve("kotlin-stdlib/$version/kotlin-stdlib-$version.jar")
|
||||
val commonStdlib = cache.resolve(
|
||||
"kotlin/kotlinTransformedMetadataLibraries/org.jetbrains.kotlin/" +
|
||||
"kotlin-stdlib/$version/test-hash/kotlin-stdlib-commonMain-$version.klib"
|
||||
)
|
||||
val jsStdlib = kotlinGroup
|
||||
.resolve("kotlin-stdlib-js/$version/kotlin-stdlib-js-$version.klib")
|
||||
val wasmJsStdlib = kotlinGroup
|
||||
.resolve("kotlin-stdlib-wasm-js/$version/kotlin-stdlib-wasm-js-$version.klib")
|
||||
val dependency = cache.resolve("dependency.jar")
|
||||
listOf(kotlinStdlib, commonStdlib, jsStdlib, wasmJsStdlib).forEach {
|
||||
it.parent.createDirectories()
|
||||
it.writeText("fixture")
|
||||
}
|
||||
dependency.writeText("fixture")
|
||||
|
||||
try {
|
||||
val classpaths = resolveDokkaAnalysisClasspaths(listOf(kotlinStdlib, dependency))
|
||||
assertEquals(listOf(commonStdlib.toString()), classpaths.commonNative)
|
||||
assertEquals(listOf(kotlinStdlib.toString(), dependency.toString()), classpaths.jvm)
|
||||
assertEquals(listOf(jsStdlib.toString()), classpaths.js)
|
||||
assertEquals(listOf(wasmJsStdlib.toString()), classpaths.wasmJs)
|
||||
} finally {
|
||||
cache.deleteRecursively()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
# KSLog GitHub release Toolchain plugin
|
||||
|
||||
Kotlin Toolchain 0.13.0 has no built-in GitHub release task. This local plugin
|
||||
keeps the existing explicit release capability as a narrow wrapper around the
|
||||
standard `gh release create` CLI.
|
||||
|
||||
It extracts the matching version section from `CHANGELOG.md`, preserves the
|
||||
legacy tag/title/target conventions, accepts the existing
|
||||
`GITHUB_RELEASE_TOKEN` variable plus standard `GH_TOKEN`/`GITHUB_TOKEN`, and
|
||||
supports a non-network `KSLOG_GITHUB_RELEASE_DRY_RUN=true` validation mode. It
|
||||
runs the committed release/version consistency preflight before rendering or
|
||||
creating a release. No CI workflow invokes it.
|
||||
|
||||
`gh release create` does not modify local git state, but GitHub may create the
|
||||
remote tag at `--target` when the requested tag does not already exist. A real
|
||||
invocation therefore mutates both the remote release and potentially the remote
|
||||
tag; dry-run mode performs neither action.
|
||||
@@ -1,15 +0,0 @@
|
||||
product: jvm/amper-plugin
|
||||
|
||||
description: Explicit GitHub release command backed by the standard GitHub CLI.
|
||||
|
||||
pluginInfo:
|
||||
id: github-release
|
||||
settingsClass: dev.inmo.kslog.build.release.GitHubReleaseSettings
|
||||
|
||||
settings:
|
||||
jvm:
|
||||
jdk:
|
||||
version: 17
|
||||
release: 17
|
||||
kotlin:
|
||||
version: 2.4.20
|
||||
@@ -1,9 +0,0 @@
|
||||
tasks:
|
||||
release:
|
||||
action: !dev.inmo.kslog.build.release.githubRelease
|
||||
projectRootDir: ${project.rootDir}
|
||||
settings: ${pluginSettings}
|
||||
|
||||
commands:
|
||||
- name: githubRelease
|
||||
performedBy: release
|
||||
@@ -1,86 +0,0 @@
|
||||
package dev.inmo.kslog.build.release
|
||||
|
||||
import org.jetbrains.amper.plugins.ExecutionAvoidance
|
||||
import org.jetbrains.amper.plugins.TaskAction
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.deleteIfExists
|
||||
import kotlin.io.path.isRegularFile
|
||||
import kotlin.io.path.pathString
|
||||
import kotlin.io.path.writeText
|
||||
|
||||
@TaskAction(ExecutionAvoidance.Disabled)
|
||||
fun githubRelease(
|
||||
projectRootDir: String,
|
||||
settings: GitHubReleaseSettings,
|
||||
) {
|
||||
val root = Path.of(projectRootDir).toAbsolutePath().normalize()
|
||||
val changelog = root.resolve(settings.changelogFile).normalize()
|
||||
require(changelog.startsWith(root) && changelog.isRegularFile()) {
|
||||
"Changelog must be an existing file inside the module: $changelog"
|
||||
}
|
||||
|
||||
val releasePreflight = root.resolve("verification/check_release_consistency.py")
|
||||
require(releasePreflight.isRegularFile()) { "Missing release consistency preflight: $releasePreflight" }
|
||||
runCommand(root, listOf("python3", releasePreflight.pathString))
|
||||
|
||||
val parser = root.resolve("changelog_parser.sh")
|
||||
require(parser.isRegularFile()) { "Missing changelog parser: $parser" }
|
||||
val notes = runCommand(root, listOf("bash", parser.pathString, settings.version, changelog.pathString))
|
||||
.trim()
|
||||
require(notes.isNotEmpty()) { "No CHANGELOG.md section found for ${settings.version}" }
|
||||
|
||||
val tag = "${settings.tagPrefix}${settings.version}"
|
||||
val targetCommitish = settings.targetCommitish.ifBlank { settings.version }
|
||||
val repository = "${settings.owner}/${settings.repository}"
|
||||
if (System.getenv("KSLOG_GITHUB_RELEASE_DRY_RUN").toBoolean()) {
|
||||
println("Dry run: GitHub release $repository tag=$tag target=$targetCommitish title=${settings.version}")
|
||||
println(notes)
|
||||
return
|
||||
}
|
||||
|
||||
val token = System.getenv("GITHUB_RELEASE_TOKEN")
|
||||
?: System.getenv("GH_TOKEN")
|
||||
?: System.getenv("GITHUB_TOKEN")
|
||||
?: error("Set GITHUB_RELEASE_TOKEN, GH_TOKEN, or GITHUB_TOKEN before creating a release")
|
||||
|
||||
val notesFile = Files.createTempFile("kslog-release-notes-", ".md")
|
||||
try {
|
||||
notesFile.writeText(notes)
|
||||
val process = ProcessBuilder(
|
||||
"gh",
|
||||
"release",
|
||||
"create",
|
||||
tag,
|
||||
"--repo",
|
||||
repository,
|
||||
"--title",
|
||||
settings.version,
|
||||
"--target",
|
||||
targetCommitish,
|
||||
"--notes-file",
|
||||
notesFile.pathString,
|
||||
)
|
||||
.directory(root.toFile())
|
||||
.redirectErrorStream(true)
|
||||
process.environment()["GH_TOKEN"] = token
|
||||
val started = process.start()
|
||||
val output = started.inputStream.bufferedReader().use { it.readText() }
|
||||
val exitCode = started.waitFor()
|
||||
check(exitCode == 0) { "gh release create failed with exit code $exitCode:\n$output" }
|
||||
print(output)
|
||||
} finally {
|
||||
notesFile.deleteIfExists()
|
||||
}
|
||||
}
|
||||
|
||||
private fun runCommand(workingDirectory: Path, command: List<String>): String {
|
||||
val process = ProcessBuilder(command)
|
||||
.directory(workingDirectory.toFile())
|
||||
.redirectErrorStream(true)
|
||||
.start()
|
||||
val output = process.inputStream.bufferedReader().use { it.readText() }
|
||||
val exitCode = process.waitFor()
|
||||
check(exitCode == 0) { "Command failed with exit code $exitCode: ${command.first()}\n$output" }
|
||||
return output
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package dev.inmo.kslog.build.release
|
||||
|
||||
import org.jetbrains.amper.plugins.Configurable
|
||||
|
||||
@Configurable
|
||||
interface GitHubReleaseSettings {
|
||||
val owner: String
|
||||
val repository: String
|
||||
val version: String
|
||||
val tagPrefix: String get() = "v"
|
||||
val targetCommitish: String get() = ""
|
||||
val changelogFile: String get() = "CHANGELOG.md"
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
modules:
|
||||
- kslog
|
||||
- plugins/dokka
|
||||
- plugins/github-release
|
||||
|
||||
plugins:
|
||||
- //plugins/dokka
|
||||
- //plugins/github-release
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,87 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Fail when KSLog's duplicated release version settings diverge."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def fail(message: str) -> None:
|
||||
raise SystemExit(f"release consistency check failed: {message}")
|
||||
|
||||
|
||||
def gradle_property(name: str) -> str:
|
||||
for raw_line in (ROOT / "gradle.properties").read_text(encoding="utf-8").splitlines():
|
||||
line = raw_line.strip()
|
||||
if not line or line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
key, value = line.split("=", 1)
|
||||
if key.strip() == name:
|
||||
return value.strip()
|
||||
fail(f"missing {name!r} in gradle.properties")
|
||||
|
||||
|
||||
def yaml_scalar(path: tuple[str, ...]) -> str:
|
||||
stack: list[tuple[int, str]] = []
|
||||
for raw_line in (ROOT / "kslog/module.yaml").read_text(encoding="utf-8").splitlines():
|
||||
if not raw_line.strip() or raw_line.lstrip().startswith("#"):
|
||||
continue
|
||||
indent = len(raw_line) - len(raw_line.lstrip(" "))
|
||||
content = raw_line.strip()
|
||||
if ":" not in content or content.startswith("-"):
|
||||
continue
|
||||
key, value = content.split(":", 1)
|
||||
while stack and stack[-1][0] >= indent:
|
||||
stack.pop()
|
||||
candidate = tuple(item[1] for item in stack) + (key,)
|
||||
value = value.strip().strip('"\'')
|
||||
if candidate == path:
|
||||
if not value:
|
||||
fail(f"empty YAML scalar at {'.'.join(path)}")
|
||||
return value
|
||||
if not value:
|
||||
stack.append((indent, key))
|
||||
fail(f"missing YAML scalar at {'.'.join(path)}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
version = gradle_property("version")
|
||||
copies = {
|
||||
"plugins.dokka.moduleVersion": yaml_scalar(("plugins", "dokka", "moduleVersion")),
|
||||
"plugins.github-release.version": yaml_scalar(("plugins", "github-release", "version")),
|
||||
"plugins.github-release.targetCommitish": yaml_scalar(
|
||||
("plugins", "github-release", "targetCommitish")
|
||||
),
|
||||
"settings.publishing.version": yaml_scalar(("settings", "publishing", "version")),
|
||||
}
|
||||
mismatches = {name: value for name, value in copies.items() if value != version}
|
||||
if mismatches:
|
||||
rendered = ", ".join(f"{name}={value!r}" for name, value in mismatches.items())
|
||||
fail(f"gradle.properties version is {version!r}, but {rendered}")
|
||||
|
||||
expected_heading = f"## {version}"
|
||||
headings = [
|
||||
(index, line)
|
||||
for index, line in enumerate(
|
||||
(ROOT / "CHANGELOG.md").read_text(encoding="utf-8").splitlines(), start=1
|
||||
)
|
||||
if line == expected_heading
|
||||
]
|
||||
if len(headings) != 1:
|
||||
fail(f"expected exactly one literal {expected_heading!r} heading, found {len(headings)}")
|
||||
|
||||
print(
|
||||
f"release consistency: PASS version={version} "
|
||||
f"copies={len(copies)} changelogLine={headings[0][0]}"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except (OSError, UnicodeError) as error:
|
||||
fail(str(error))
|
||||
@@ -1,42 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Verify wrapper checkout attributes and on-disk line endings."""
|
||||
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def fail(message: str) -> None:
|
||||
raise SystemExit(f"wrapper line-ending check failed: {message}")
|
||||
|
||||
|
||||
shell_bytes = (ROOT / "kotlin").read_bytes()
|
||||
batch_bytes = (ROOT / "kotlin.bat").read_bytes()
|
||||
if b"\r" in shell_bytes:
|
||||
fail("kotlin must contain LF line endings only")
|
||||
if b"\r\n" not in batch_bytes or b"\n" in batch_bytes.replace(b"\r\n", b""):
|
||||
fail("kotlin.bat must contain CRLF line endings only")
|
||||
|
||||
completed = subprocess.run(
|
||||
["git", "-C", str(ROOT), "check-attr", "text", "eol", "--", "kotlin", "kotlin.bat"],
|
||||
check=True,
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
)
|
||||
attributes = {}
|
||||
for line in completed.stdout.splitlines():
|
||||
path, attribute, value = line.split(": ", 2)
|
||||
attributes[(path, attribute)] = value
|
||||
|
||||
expected = {
|
||||
("kotlin", "text"): "set",
|
||||
("kotlin", "eol"): "lf",
|
||||
("kotlin.bat", "text"): "set",
|
||||
("kotlin.bat", "eol"): "crlf",
|
||||
}
|
||||
if attributes != expected:
|
||||
fail(f"unexpected attributes: {attributes}")
|
||||
|
||||
print("wrapper line endings: PASS kotlin=LF kotlin.bat=CRLF checkoutAttributes=PASS")
|
||||
@@ -1,39 +0,0 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform" version "2.4.20"
|
||||
id "com.android.kotlin.multiplatform.library" version "9.1.1"
|
||||
}
|
||||
|
||||
def externalBuildDir = providers.environmentVariable("KSLOG_CONSUMER_BUILD_DIR").orNull
|
||||
if (externalBuildDir != null) {
|
||||
layout.buildDirectory.set(file(externalBuildDir))
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
js {
|
||||
nodejs()
|
||||
}
|
||||
wasmJs {
|
||||
nodejs()
|
||||
}
|
||||
linuxX64()
|
||||
linuxArm64()
|
||||
mingwX64()
|
||||
android {
|
||||
namespace = "dev.inmo.kslog.verification"
|
||||
compileSdk {
|
||||
version = release(37)
|
||||
}
|
||||
minSdk {
|
||||
version = release(21)
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
implementation "dev.inmo:kslog:2.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
def testRepository = providers.environmentVariable("KSLOG_TEST_REPOSITORY").orNull
|
||||
if (testRepository == null) {
|
||||
throw new GradleException("KSLOG_TEST_REPOSITORY must point to an isolated Maven repository")
|
||||
}
|
||||
maven { url = uri(testRepository) }
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "kslog-publication-consumer"
|
||||
@@ -1,14 +0,0 @@
|
||||
package dev.inmo.kslog.verification
|
||||
|
||||
import dev.inmo.kslog.common.CallbackKSLog
|
||||
import dev.inmo.kslog.common.KSLog
|
||||
import dev.inmo.kslog.common.LogLevel
|
||||
|
||||
fun exercisePublishedApi(): String {
|
||||
var captured = ""
|
||||
val logger: KSLog = CallbackKSLog { level, tag, message, throwable ->
|
||||
captured = listOf(level, tag, message, throwable).joinToString("|")
|
||||
}
|
||||
logger.performLog(LogLevel.INFO, "consumer", "message", null)
|
||||
return captured
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
root_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
fixture_dir=$(mktemp -d /tmp/kslog-changelog-parser.XXXXXX)
|
||||
trap 'rm -rf -- "$fixture_dir"' EXIT
|
||||
|
||||
cat > "$fixture_dir/CHANGELOG.md" <<'EOF'
|
||||
# Changelog
|
||||
|
||||
## 2x1x0
|
||||
|
||||
regex lookalike must not match
|
||||
|
||||
## 2.1.0
|
||||
|
||||
literal section
|
||||
|
||||
## 2.0.0
|
||||
|
||||
older section
|
||||
EOF
|
||||
|
||||
actual=$(bash "$root_dir/changelog_parser.sh" 2.1.0 "$fixture_dir/CHANGELOG.md")
|
||||
if [[ "$actual" != $'\nliteral section' ]]; then
|
||||
printf 'unexpected parser output:\n%s\n' "$actual" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
stdin_actual=$(bash "$root_dir/changelog_parser.sh" 2.1.0 < "$fixture_dir/CHANGELOG.md")
|
||||
if [[ "$stdin_actual" != "$actual" ]]; then
|
||||
printf 'stdin/file parser behavior differs\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if bash "$root_dir/changelog_parser.sh" 9.9.9 "$fixture_dir/CHANGELOG.md" >/dev/null 2>&1; then
|
||||
printf 'missing literal heading unexpectedly succeeded\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf 'changelog parser: PASS literal heading selected; regex lookalike rejected\n'
|
||||
@@ -1,110 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
root_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
base_commit=${KSLOG_MIGRATION_BASE_COMMIT:-0b9fb30862c7319bb228039ee33eef9a0ddef95c}
|
||||
work_dir=$(mktemp -d /tmp/kslog-gradle-verifier.XXXXXX)
|
||||
|
||||
cleanup() {
|
||||
if [[ "${KSLOG_KEEP_VERIFICATION_TEMP:-false}" == "true" ]]; then
|
||||
printf 'verification workspace retained at %s\n' "$work_dir"
|
||||
else
|
||||
rm -rf -- "$work_dir"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
for command_name in git tar python3; do
|
||||
command -v "$command_name" >/dev/null || {
|
||||
printf 'required command is unavailable: %s\n' "$command_name" >&2
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
|
||||
git -C "$root_dir" cat-file -e "$base_commit^{commit}"
|
||||
|
||||
baseline_dir="$work_dir/baseline"
|
||||
baseline_repo="$work_dir/baseline-m2"
|
||||
current_repo="$work_dir/current-m2"
|
||||
gradle_home="$work_dir/gradle-home"
|
||||
gnupg_home="$work_dir/gnupg"
|
||||
archive="$work_dir/baseline.tar"
|
||||
mkdir -p "$baseline_dir" "$baseline_repo" "$current_repo" "$gradle_home" "$gnupg_home"
|
||||
chmod 700 "$gnupg_home"
|
||||
|
||||
git -C "$root_dir" archive --format=tar --output="$archive" "$base_commit"
|
||||
tar -xf "$archive" -C "$baseline_dir"
|
||||
|
||||
cache_source=${KSLOG_GRADLE_CACHE_SOURCE:-${GRADLE_USER_HOME:-${HOME}/.gradle}}
|
||||
for cache_entry in caches wrapper; do
|
||||
if [[ -d "$cache_source/$cache_entry" ]]; then
|
||||
ln -s "$cache_source/$cache_entry" "$gradle_home/$cache_entry"
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -e "$gradle_home/gradle.properties" || -e "$gradle_home/init.d" ]]; then
|
||||
printf 'isolated Gradle home unexpectedly contains user configuration\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
run_publication() {
|
||||
local project_dir=$1
|
||||
local repository_dir=$2
|
||||
local log_file=$3
|
||||
|
||||
(
|
||||
cd "$project_dir"
|
||||
unset \
|
||||
GITHUB_RELEASE_TOKEN GH_TOKEN GITHUB_TOKEN \
|
||||
INMONEXUS_USER INMONEXUS_PASSWORD \
|
||||
SONATYPE_USER SONATYPE_PASSWORD PUBLISHING_TYPE \
|
||||
KOTLIN_TOOLCHAIN_SIGNING_KEY KOTLIN_TOOLCHAIN_SIGNING_KEY_PASSPHRASE \
|
||||
GRADLE_OPTS JAVA_TOOL_OPTIONS _JAVA_OPTIONS
|
||||
export GRADLE_USER_HOME="$gradle_home"
|
||||
export GNUPGHOME="$gnupg_home"
|
||||
./gradlew --no-daemon \
|
||||
-Dmaven.repo.local="$repository_dir" \
|
||||
clean publishToMavenLocal
|
||||
) | tee "$log_file"
|
||||
|
||||
if find "$repository_dir" -type f -name '*.asc' -print -quit | grep -q .; then
|
||||
printf 'isolated publication unexpectedly produced signatures: %s\n' "$repository_dir" >&2
|
||||
exit 1
|
||||
fi
|
||||
if grep -Eq '^> Task :sign[A-Z]' "$log_file"; then
|
||||
printf 'isolated publication unexpectedly executed a signing task\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
printf 'Publishing untouched baseline %s to an isolated repository\n' "$base_commit"
|
||||
run_publication "$baseline_dir" "$baseline_repo" "$work_dir/baseline-publish.log"
|
||||
|
||||
printf 'Publishing post-move Gradle fallback to an isolated repository\n'
|
||||
run_publication "$root_dir" "$current_repo" "$work_dir/current-publish.log"
|
||||
|
||||
python3 "$root_dir/verification/verify_gradle_publication.py" \
|
||||
--project-root "$root_dir" \
|
||||
--base-commit "$base_commit" \
|
||||
--baseline-repository "$baseline_repo" \
|
||||
--current-repository "$current_repo"
|
||||
|
||||
printf 'Compiling the committed consumer probe against the post-move publication\n'
|
||||
(
|
||||
unset \
|
||||
GITHUB_RELEASE_TOKEN GH_TOKEN GITHUB_TOKEN \
|
||||
INMONEXUS_USER INMONEXUS_PASSWORD \
|
||||
SONATYPE_USER SONATYPE_PASSWORD PUBLISHING_TYPE \
|
||||
KOTLIN_TOOLCHAIN_SIGNING_KEY KOTLIN_TOOLCHAIN_SIGNING_KEY_PASSPHRASE \
|
||||
GRADLE_OPTS JAVA_TOOL_OPTIONS _JAVA_OPTIONS
|
||||
export GRADLE_USER_HOME="$gradle_home"
|
||||
export GNUPGHOME="$gnupg_home"
|
||||
export KSLOG_TEST_REPOSITORY="$current_repo"
|
||||
export KSLOG_CONSUMER_BUILD_DIR="$work_dir/consumer-build"
|
||||
"$root_dir/gradlew" --no-daemon \
|
||||
--project-dir "$root_dir/verification/consumer" \
|
||||
--project-cache-dir "$work_dir/consumer-project-cache" \
|
||||
assemble
|
||||
) | tee "$work_dir/consumer.log"
|
||||
|
||||
printf 'Gradle fallback verification: PASS baseline=%s primaryFiles=46 consumer=PASS\n' "$base_commit"
|
||||
@@ -1,292 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compare post-move Gradle publication/API output with the untouched base."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import xml.etree.ElementTree as ET
|
||||
import zipfile
|
||||
|
||||
|
||||
EXPECTED_ARTIFACT_IDS = {
|
||||
"kslog",
|
||||
"kslog-android",
|
||||
"kslog-js",
|
||||
"kslog-jvm",
|
||||
"kslog-linuxarm32hfp",
|
||||
"kslog-linuxarm64",
|
||||
"kslog-linuxx64",
|
||||
"kslog-mingwx64",
|
||||
"kslog-wasm-js",
|
||||
}
|
||||
EXPECTED_VERSION = "2.1.0"
|
||||
SOURCE_ROOTS = {
|
||||
"kslog/src": "src/commonMain/kotlin",
|
||||
"kslog/src@android": "src/androidMain/kotlin",
|
||||
"kslog/src@js": "src/jsMain/kotlin",
|
||||
"kslog/src@jvm": "src/jvmMain/kotlin",
|
||||
"kslog/src@native": "src/nativeMain/kotlin",
|
||||
"kslog/src@wasmJs": "src/wasmJsMain/kotlin",
|
||||
}
|
||||
|
||||
|
||||
def fail(message: str) -> None:
|
||||
raise SystemExit(f"Gradle fallback verification failed: {message}")
|
||||
|
||||
|
||||
def primary_files(repository: Path) -> dict[str, Path]:
|
||||
group_root = repository / "dev/inmo"
|
||||
if not group_root.is_dir():
|
||||
fail(f"missing publication group directory: {group_root}")
|
||||
ignored_names = {"maven-metadata-local.xml", "_remote.repositories"}
|
||||
ignored_suffixes = (".asc", ".md5", ".sha1", ".sha256", ".sha512", ".lastUpdated")
|
||||
result = {}
|
||||
for path in sorted(group_root.rglob("*")):
|
||||
if not path.is_file() or path.name in ignored_names or path.name.endswith(ignored_suffixes):
|
||||
continue
|
||||
result[path.relative_to(group_root).as_posix()] = path
|
||||
return result
|
||||
|
||||
|
||||
def zip_payload(path: Path) -> dict[str, str]:
|
||||
with zipfile.ZipFile(path) as archive:
|
||||
return {
|
||||
info.filename: hashlib.sha256(archive.read(info)).hexdigest()
|
||||
for info in archive.infolist()
|
||||
if not info.is_dir() and info.filename != "META-INF/MANIFEST.MF"
|
||||
}
|
||||
|
||||
|
||||
def xml_text(element: ET.Element, child: str) -> str:
|
||||
found = element.find(f"{{*}}{child}")
|
||||
return "" if found is None or found.text is None else found.text.strip()
|
||||
|
||||
|
||||
def pom_model(path: Path) -> dict[str, object]:
|
||||
root = ET.parse(path).getroot()
|
||||
|
||||
def records(container: str, record: str, fields: tuple[str, ...]) -> tuple[tuple[str, ...], ...]:
|
||||
parent = root.find(f"{{*}}{container}")
|
||||
if parent is None:
|
||||
return ()
|
||||
return tuple(
|
||||
sorted(tuple(xml_text(item, field) for field in fields) for item in parent.findall(f"{{*}}{record}"))
|
||||
)
|
||||
|
||||
scm = root.find("{*}scm")
|
||||
return {
|
||||
"groupId": xml_text(root, "groupId"),
|
||||
"artifactId": xml_text(root, "artifactId"),
|
||||
"version": xml_text(root, "version"),
|
||||
"packaging": xml_text(root, "packaging"),
|
||||
"name": xml_text(root, "name"),
|
||||
"description": xml_text(root, "description"),
|
||||
"url": xml_text(root, "url"),
|
||||
"licenses": records("licenses", "license", ("name", "url")),
|
||||
"developers": records("developers", "developer", ("id", "name", "email")),
|
||||
"scm": ()
|
||||
if scm is None
|
||||
else (
|
||||
xml_text(scm, "url"),
|
||||
xml_text(scm, "connection"),
|
||||
xml_text(scm, "developerConnection"),
|
||||
),
|
||||
"dependencies": records(
|
||||
"dependencies",
|
||||
"dependency",
|
||||
("groupId", "artifactId", "version", "scope", "type", "classifier", "optional"),
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def validate_required_pom(path: Path, model: dict[str, object]) -> None:
|
||||
expected_artifact_id = path.parent.parent.name
|
||||
required = {
|
||||
"groupId": "dev.inmo",
|
||||
"artifactId": expected_artifact_id,
|
||||
"version": EXPECTED_VERSION,
|
||||
"name": "kslog",
|
||||
"description": "Simple logging library for kotlin",
|
||||
"url": "https://github.com/InsanusMokrassar/KSLog",
|
||||
"licenses": (("MIT License", "https://opensource.org/licenses/MIT"),),
|
||||
"developers": (
|
||||
("Akkihi", "Akkihi", "akkihiguy@gmail.com"),
|
||||
("InsanusMokrassar", "InsanusMokrassar", "ovsyannikov.alexey95@gmail.com"),
|
||||
),
|
||||
"scm": (
|
||||
"https://github.com/InsanusMokrassar/KSLog.git",
|
||||
"",
|
||||
"scm:git:[fetch=]https://github.com/InsanusMokrassar/KSLog.git[push=]https://github.com/InsanusMokrassar/KSLog.git",
|
||||
),
|
||||
}
|
||||
for field, expected in required.items():
|
||||
if model[field] != expected:
|
||||
fail(f"required POM metadata mismatch in {path}: {field}={model[field]!r}")
|
||||
|
||||
|
||||
def compare_publications(baseline_repository: Path, current_repository: Path) -> None:
|
||||
baseline = primary_files(baseline_repository)
|
||||
current = primary_files(current_repository)
|
||||
if len(baseline) != 46 or len(current) != 46:
|
||||
fail(f"expected 46 primary files, got baseline={len(baseline)} current={len(current)}")
|
||||
if baseline.keys() != current.keys():
|
||||
missing = sorted(baseline.keys() - current.keys())
|
||||
added = sorted(current.keys() - baseline.keys())
|
||||
fail(f"publication inventory differs; missing={missing}, added={added}")
|
||||
|
||||
artifact_ids = {relative.split("/", 1)[0] for relative in baseline}
|
||||
if artifact_ids != EXPECTED_ARTIFACT_IDS:
|
||||
fail(f"published artifact IDs differ: {sorted(artifact_ids)}")
|
||||
|
||||
source_jars = [relative for relative in baseline if relative.endswith("-sources.jar")]
|
||||
if len(source_jars) != 9:
|
||||
fail(f"expected 9 source JARs, found {len(source_jars)}")
|
||||
for relative in source_jars:
|
||||
if zip_payload(baseline[relative]) != zip_payload(current[relative]):
|
||||
fail(f"source JAR payload differs: {relative}")
|
||||
|
||||
pom_paths = [relative for relative in baseline if relative.endswith(".pom")]
|
||||
if len(pom_paths) != 9:
|
||||
fail(f"expected 9 POMs, found {len(pom_paths)}")
|
||||
for relative in pom_paths:
|
||||
baseline_model = pom_model(baseline[relative])
|
||||
current_model = pom_model(current[relative])
|
||||
validate_required_pom(baseline[relative], baseline_model)
|
||||
validate_required_pom(current[relative], current_model)
|
||||
if baseline_model != current_model:
|
||||
fail(f"POM model differs: {relative}")
|
||||
|
||||
print("publication inventory: PASS primaryFiles=46 coordinates=9 sourceJars=9 poms=9")
|
||||
|
||||
|
||||
def verify_source_bytes(project_root: Path, base_commit: str) -> None:
|
||||
checked = 0
|
||||
for current_root, previous_root in SOURCE_ROOTS.items():
|
||||
for current_path in sorted((project_root / current_root).rglob("*.kt")):
|
||||
relative = current_path.relative_to(project_root / current_root)
|
||||
previous_path = f"{previous_root}/{relative.as_posix()}"
|
||||
completed = subprocess.run(
|
||||
["git", "-C", str(project_root), "show", f"{base_commit}:{previous_path}"],
|
||||
check=False,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
if completed.returncode != 0:
|
||||
fail(f"base source is missing: {previous_path}")
|
||||
if completed.stdout != current_path.read_bytes():
|
||||
fail(f"production source bytes changed: {current_path.relative_to(project_root)}")
|
||||
checked += 1
|
||||
if checked != 20:
|
||||
fail(f"expected 20 production sources, checked {checked}")
|
||||
print("production source identity: PASS files=20")
|
||||
|
||||
|
||||
def javap_path() -> str:
|
||||
java_home = Path(os.environ.get("JAVA_HOME", ""))
|
||||
candidate = java_home / "bin/javap"
|
||||
if candidate.is_file():
|
||||
return str(candidate)
|
||||
found = shutil.which("javap")
|
||||
if found is None:
|
||||
fail("javap is unavailable; set JAVA_HOME to a JDK")
|
||||
return found
|
||||
|
||||
|
||||
def public_api(archive_path: Path) -> tuple[tuple[str, ...], str, bytes]:
|
||||
with zipfile.ZipFile(archive_path) as archive:
|
||||
class_names = tuple(
|
||||
sorted(
|
||||
info.filename[:-6].replace("/", ".")
|
||||
for info in archive.infolist()
|
||||
if info.filename.endswith(".class") and not info.filename.startswith("META-INF/")
|
||||
)
|
||||
)
|
||||
module_entry = "META-INF/dev.inmo_kslog.kotlin_module"
|
||||
if module_entry not in archive.namelist():
|
||||
fail(f"missing Kotlin module identity in {archive_path}")
|
||||
module_bytes = archive.read(module_entry)
|
||||
|
||||
output = []
|
||||
for class_name in class_names:
|
||||
completed = subprocess.run(
|
||||
[javap_path(), "-public", "-s", "-constants", "-classpath", str(archive_path), class_name],
|
||||
check=False,
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
if completed.returncode != 0:
|
||||
fail(f"javap failed for {class_name}: {completed.stderr.strip()}")
|
||||
output.append(completed.stdout)
|
||||
return class_names, "\n".join(output), module_bytes
|
||||
|
||||
|
||||
def nested_classes_jar(aar: Path, destination: Path) -> Path:
|
||||
with zipfile.ZipFile(aar) as archive:
|
||||
try:
|
||||
classes = archive.read("classes.jar")
|
||||
except KeyError as error:
|
||||
fail(f"Gradle AAR lacks classes.jar: {aar}")
|
||||
destination.write_bytes(classes)
|
||||
return destination
|
||||
|
||||
|
||||
def compare_public_jvm_signatures(baseline_repository: Path, current_repository: Path) -> None:
|
||||
group = Path("dev/inmo")
|
||||
jvm_relative = group / "kslog-jvm/2.1.0/kslog-jvm-2.1.0.jar"
|
||||
android_relative = group / "kslog-android/2.1.0/kslog-android-2.1.0.aar"
|
||||
|
||||
baseline_jvm = public_api(baseline_repository / jvm_relative)
|
||||
current_jvm = public_api(current_repository / jvm_relative)
|
||||
if baseline_jvm != current_jvm:
|
||||
fail("JVM class list, public method/field signatures, or Kotlin module bytes differ")
|
||||
if len(current_jvm[0]) != 25:
|
||||
fail(f"expected 25 JVM classes, found {len(current_jvm[0])}")
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="kslog-android-api-") as temporary:
|
||||
temporary_path = Path(temporary)
|
||||
baseline_android = public_api(
|
||||
nested_classes_jar(baseline_repository / android_relative, temporary_path / "baseline.jar")
|
||||
)
|
||||
current_android = public_api(
|
||||
nested_classes_jar(current_repository / android_relative, temporary_path / "current.jar")
|
||||
)
|
||||
if baseline_android != current_android:
|
||||
fail("Android class list, public method/field signatures, or Kotlin module bytes differ")
|
||||
if len(current_android[0]) != 24:
|
||||
fail(f"expected 24 Android classes, found {len(current_android[0])}")
|
||||
|
||||
with zipfile.ZipFile(baseline_repository / android_relative) as baseline_aar:
|
||||
baseline_manifest = baseline_aar.read("AndroidManifest.xml")
|
||||
with zipfile.ZipFile(current_repository / android_relative) as current_aar:
|
||||
current_manifest = current_aar.read("AndroidManifest.xml")
|
||||
if baseline_manifest != current_manifest:
|
||||
fail("post-move Gradle AndroidManifest.xml differs from the untouched baseline")
|
||||
|
||||
print("public JVM signatures: PASS jvmClasses=25 androidClasses=24 module=dev.inmo_kslog")
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--project-root", type=Path, required=True)
|
||||
parser.add_argument("--base-commit", required=True)
|
||||
parser.add_argument("--baseline-repository", type=Path, required=True)
|
||||
parser.add_argument("--current-repository", type=Path, required=True)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
compare_publications(args.baseline_repository, args.current_repository)
|
||||
verify_source_bytes(args.project_root, args.base_commit)
|
||||
compare_public_jvm_signatures(args.baseline_repository, args.current_repository)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user