diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..111886ad --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +# GraalVM native-image reachability metadata is generated by the tracing agent (see core/README.md +# and regenerate_native_metadata.sh). Mark it generated so code review collapses the diff and it's +# excluded from language stats. +core/src/main/native-image/resources/**/*.json linguist-generated=true diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index ebea63c6..392beac0 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -4,6 +4,8 @@ on: push: paths-ignore: - '**.md' + branches: + - main pull_request: paths-ignore: - '**.md' @@ -31,3 +33,56 @@ jobs: run: ./gradlew :lambda:build --stacktrace --no-daemon - name: Build ktfmt run: ./gradlew :ktfmt:build --stacktrace --no-daemon + + native: + # The linux build is blocking so stale GraalVM reachability metadata (which is OS-independent) + # fails CI. macOS and Windows stay non-blocking, since cross-platform toolchain hiccups + # shouldn't fail the run. Release binaries are built separately (publish_artifacts_on_release.yaml). + # Linux uses the oldest available ubuntu for broader glibc compatibility. + name: Native Image on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + continue-on-error: ${{ matrix.os != 'ubuntu-22.04' }} + # On Windows runners the workspace is on D: but java.io.tmpdir defaults to C:. native-build-tools + # writes the native-image @argfile under java.io.tmpdir, then relativizes it against the build's + # working directory (on D:), which fails with "'other' has different root". Point temp at the + # workspace drive so both share a root. Empty (no-op) on other OSes. + env: + TMP: ${{ matrix.os == 'windows-latest' && format('{0}\tmp', github.workspace) || '' }} + TEMP: ${{ matrix.os == 'windows-latest' && format('{0}\tmp', github.workspace) || '' }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-22.04, macos-latest, windows-latest] + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Create temp dir on workspace drive (Windows) + if: matrix.os == 'windows-latest' + run: New-Item -ItemType Directory -Force -Path "${{ github.workspace }}\tmp" + + # Gradle runs on the project's JDK (17); native-image is provided separately via GRAALVM_HOME + # (set-java-home: false keeps JAVA_HOME pointed at the JDK above). + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: zulu + + - name: Set up GraalVM + uses: graalvm/setup-graalvm@v1 + with: + java-version: '25' + distribution: 'graalvm-community' + github-token: ${{ secrets.GITHUB_TOKEN }} + set-java-home: 'false' + native-image-job-reports: 'true' + + - name: Build + shell: bash + run: ./gradlew :ktfmt:nativeCompile --stacktrace --no-daemon + + - name: Smoke test + shell: bash + run: ./native_smoke_test.sh diff --git a/.github/workflows/publish_artifacts_on_release.yaml b/.github/workflows/publish_artifacts_on_release.yaml index f9ef2d22..8e19bd8f 100644 --- a/.github/workflows/publish_artifacts_on_release.yaml +++ b/.github/workflows/publish_artifacts_on_release.yaml @@ -83,6 +83,76 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Build the GraalVM native binary per platform and attach it to the GitHub Release. Modeled on + # google-java-format's release workflow. Uses GraalVM Community (Serial GC only, which matches the + # build's default) so there are no Oracle GraalVM licensing considerations. + upload_native_binaries: + name: Native binary (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + permissions: + contents: write # Required to upload release assets + strategy: + fail-fast: false + matrix: + # Build linux on the oldest available ubuntu for broader glibc compatibility. Keep the keys + # of the SUFFIX map below in sync with this list. + os: [ubuntu-22.04, ubuntu-22.04-arm, macos-latest, windows-latest] + env: + SUFFIX: ${{ fromJson('{"ubuntu-22.04":"linux-x86-64","ubuntu-22.04-arm":"linux-arm64","macos-latest":"darwin-arm64","windows-latest":"windows-x86-64"}')[matrix.os] }} + EXTENSION: ${{ matrix.os == 'windows-latest' && '.exe' || '' }} + # On Windows the workspace is on D: but java.io.tmpdir defaults to C:. native-build-tools writes + # the native-image @argfile under java.io.tmpdir, then relativizes it against the build's working + # directory (on D:), which fails with "'other' has different root". Point temp at the workspace + # drive so both share a root. Empty (no-op) on other OSes. + TMP: ${{ matrix.os == 'windows-latest' && format('{0}\tmp', github.workspace) || '' }} + TEMP: ${{ matrix.os == 'windows-latest' && format('{0}\tmp', github.workspace) || '' }} + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.events.release.tag_name || inputs.release_tag || github.ref }} + + - name: Create temp dir on workspace drive (Windows) + if: matrix.os == 'windows-latest' + run: New-Item -ItemType Directory -Force -Path "${{ github.workspace }}\tmp" + + # Gradle runs on JDK 17; native-image is provided via GRAALVM_HOME (set-java-home: false). + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: zulu + + - name: Set up GraalVM + uses: graalvm/setup-graalvm@v1 + with: + java-version: '25' + distribution: 'graalvm-community' + github-token: ${{ secrets.GITHUB_TOKEN }} + set-java-home: 'false' + native-image-job-reports: 'true' + + - name: Build native binary + shell: bash + run: ./gradlew :ktfmt:nativeCompile -Pktfmt.native.release=true --stacktrace --no-daemon + + - name: Smoke test + shell: bash + run: ./native_smoke_test.sh + + - name: Stage binary + shell: bash + run: | + mkdir -p release + cp "core/build/native/nativeCompile/ktfmt${EXTENSION}" "release/ktfmt_${SUFFIX}${EXTENSION}" + + - name: Upload native binary to GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ github.events.release.tag_name || inputs.release_tag }} + files: release/ktfmt_${{ env.SUFFIX }}${{ env.EXTENSION }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + deploy_website: runs-on: ubuntu-latest steps: diff --git a/.gitignore b/.gitignore index 2ab151ad..e66a16ce 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,7 @@ release-ktfmt-website/ **/build/ !src/**/build/ .claude/ + +# Native Image profiles are large +core/src/main/native-image/profiles/*.iprof + diff --git a/CHANGELOG.md b/CHANGELOG.md index 0674e29a..1f5c0989 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - Remove forced breaking of `context` function types (https://github.com/facebook/ktfmt/pull/613) - Preserve user-authored line breaks inside lambda bodies by default, rather than have ktfmt impose anything. This can be particularly useful for DSL syntax like Compose UI or Kotlin Gradle script. The behavior follows the `FormattingOptions.preserveLambdaBreaks` setting of the chosen style. (https://github.com/facebook/ktfmt/pull/614) - Add a `FormattingOptions.Builder` API for tools to avoid breaking ABI changes with new options. (https://github.com/facebook/ktfmt/pull/614) +- Add standalone native binaries built with GraalVM `native-image`. These have identical CLI behavior to the JVM version but with near-instant startup and no JVM required (https://github.com/facebook/ktfmt/issues/441) ## [0.62] ### Added diff --git a/README.md b/README.md index c6f26802..8d80b2d8 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,32 @@ val state = remember { mutableStateOf(false) } or limited `.editorconfig` support). This is a deliberate design decision to unify our code formatting on a single format.* +### Native binary + +`ktfmt` is also available as a standalone native binary, compiled ahead-of-time with +[GraalVM `native-image`](https://www.graalvm.org/latest/reference-manual/native-image/). It behaves +exactly like the JVM CLI (same options) but starts near-instantly and needs no JVM installed, which +is especially handy for pre-commit hooks and formatting only changed files. Pre-built binaries for +each supported platform are attached to the [releases page](https://github.com/facebook/ktfmt/releases): + +``` +$ ktfmt [--kotlinlang-style | --google-style] [files...] +``` + +#### Building the native binary from source + +Building requires a [GraalVM](https://www.graalvm.org/downloads/) JDK (one that includes +`native-image`) discoverable via `GRAALVM_HOME` or `PATH`: + +``` +$ ./gradlew :ktfmt:nativeCompile +$ ./core/build/native/nativeCompile/ktfmt --version +``` + +The build defaults to the Serial GC and a broadly-compatible `-march` target. Several knobs are +exposed via `-Pktfmt.native.*` properties (GC, target architecture, PGO, static linking, …); see +[`build-logic/src/main/kotlin/ktfmt.native-image.gradle.kts`](build-logic/src/main/kotlin/ktfmt.native-image.gradle.kts). + ### using Gradle A [Gradle plugin (ktfmt-gradle)](https://github.com/cortinico/ktfmt-gradle) is available on the diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts new file mode 100644 index 00000000..20870a05 --- /dev/null +++ b/build-logic/build.gradle.kts @@ -0,0 +1,30 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ + +plugins { `kotlin-dsl` } + +repositories { + gradlePluginPortal() + mavenCentral() +} + +dependencies { + // Makes the GraalVM Native Build Tools plugin (and its `graalvmNative` DSL) available to the + // convention plugins defined in this build. Version is read from the shared catalog. + implementation( + libs.plugins.graalvm.get().run { "$pluginId:$pluginId.gradle.plugin:$version" } + ) +} diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts new file mode 100644 index 00000000..45048f10 --- /dev/null +++ b/build-logic/settings.gradle.kts @@ -0,0 +1,33 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ + +dependencyResolutionManagement { + repositories { + gradlePluginPortal() + mavenCentral() + } + versionCatalogs { + create("libs") { + from(files("../gradle/libs.versions.toml")) + // The shared catalog declares a `ktfmt` library whose version is injected at runtime by the + // root build's settings. Supply a placeholder so the catalog validates when consumed here; + // the `ktfmt` library itself is never used by build-logic. + version("ktfmt", "0.0.0") + } + } +} + +rootProject.name = "build-logic" diff --git a/build-logic/src/main/kotlin/ktfmt.native-image.gradle.kts b/build-logic/src/main/kotlin/ktfmt.native-image.gradle.kts new file mode 100644 index 00000000..0cc5e652 --- /dev/null +++ b/build-logic/src/main/kotlin/ktfmt.native-image.gradle.kts @@ -0,0 +1,241 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ + +// Convention plugin (`id("ktfmt.native-image")`) owning ktfmt's GraalVM Native Image config: image +// flags, GC/arch knobs, and PGO. The native source set, helper tasks, and native deps stay in +// core/build.gradle.kts (they use the version catalog); this plugin references them lazily. + +import java.nio.file.Paths +import org.graalvm.buildtools.gradle.dsl.GraalVMExtension +import org.gradle.api.tasks.bundling.Jar + +plugins { id("org.graalvm.buildtools.native") } + +// Entry point for the native binary. Set directly on the GraalVM binary below; we intentionally do +// not apply the `application` plugin, which would only add unused `run`/`distZip`/`distTar` tasks. +val entrypoint = "com.facebook.ktfmt.cli.Main" + +// Pass `-Pktfmt.native.release=true` to enable release mode for Native Image. +val nativeRelease = findProperty("ktfmt.native.release") == "true" + +// Pass `-Pktfmt.native.target=xx` to pass `-march=xx` to Native Image. Defaults to `compatibility` +// (runs on any CPU of the build architecture), matching google-java-format. Pass `native` to target +// this exact machine, or e.g. `x86-64-v3` to trade portability for performance. +val nativeTarget = findProperty("ktfmt.native.target") ?: "compatibility" + +// Pass `-Pktfmt.native.gc=xx` to select a garbage collector; options include `serial`, `G1`, and +// `epsilon`. Defaults to `serial` because: +// - it's the best fit for ktfmt's short-lived CLI runs +// - it's the only GC available in GraalVM Community Edition +// - it's the only GC supported by `native-image` on macOS +// - avoids the Oracle GraalVM licensing considerations of `G1`. Anyone that needs this can do it +// from source and opt into `G1` for large, long-running batch formatting on Linux with Oracle +// GraalVM. +val nativeGc = findProperty("ktfmt.native.gc") ?: "serial" + +// Pass `-Pktfmt.native.debug=true` to build the Native Image binary with debug info. +val nativeDebug = findProperty("ktfmt.native.debug") == "true" + +// Pass `-Pktfmt.native.lto=true` to enable LTO for the Native Image binary. +val enableLto = findProperty("ktfmt.native.lto") == "true" + +// Pass `-Pktfmt.native.muslHome=xx` or set MUSL_HOME to point to the Musl sysroot when building for +// Musl Libc. +val muslSysroot = (findProperty("ktfmt.native.muslHome") ?: System.getenv("MUSL_HOME"))?.toString() + +// Pass `-Pktfmt.native.musl=true` to build a fully-static binary against Musl Libc. +val preferMusl = + (findProperty("ktfmt.native.musl") == "true").also { preferMusl -> + require(!preferMusl || muslSysroot != null) { + "When `ktfmt.native.musl` is true, -Pktfmt.native.muslHome or MUSL_HOME must be set to the Musl sysroot. " + + "See https://www.graalvm.org/latest/reference-manual/native-image/guides/build-static-executables/" + } + } + +// Pass `-Pktfmt.native.smol=true` to build a small, instead of a fast, binary. +val preferSmol = (findProperty("ktfmt.native.smol") == "true") + +// Pass `-Pktfmt.native.opt=s` to pass e.g. `-Os` to Native Image. +val nativeOpt = + when (val opt = findProperty("ktfmt.native.opt")) { + null -> + when { + preferSmol -> "s" + nativeRelease -> "3" + else -> "b" // prefer build speed + } + else -> opt + } + +// PGO profiles live in `src/main/native-image/profiles`. They are git-ignored (see `.gitignore`) +// and are generated via `pgo_train.sh` or materialized in CI, so they may be absent. Only profiles +// that actually exist on disk are used; a missing profile degrades to a non-PGO build instead of +// failing `native-image` with a file-not-found. +val existingPgoProfiles = + listOf("default.iprof") + .map { profileName -> + layout.projectDirectory + .file(Paths.get("src", "main", "native-image", "profiles", profileName).toString()) + .asFile + } + .filter { it.exists() } + +val pgoProfileArgs = + if (existingPgoProfiles.isEmpty()) emptyList() + else listOf("--pgo=${existingPgoProfiles.joinToString(",") { it.absolutePath }}") + +// Pass `-Pktfmt.native.pgo=true` to build with PGO; pass `train` to enable instrumentation. +val pgoArgs = + when (val pgo = findProperty("ktfmt.native.pgo")) { + null -> if (nativeRelease) pgoProfileArgs else emptyList() + "true" -> pgoProfileArgs + "false" -> emptyList() + "train" -> listOf("--pgo-instrument") + else -> error("Unrecognized `ktfmt.native.pgo` argument: '$pgo'") + } + +// Warn (rather than fail) when PGO was requested but no profiles are available. +if ( + (nativeRelease || findProperty("ktfmt.native.pgo") == "true") && existingPgoProfiles.isEmpty() +) { + logger.warn( + "[ktfmt native] PGO was requested but no profiles were found in " + + "src/main/native-image/profiles; building without PGO. Generate one with `bash pgo_train.sh`." + ) +} + +configure { + binaries { + named("main") { + imageName.set("ktfmt") + mainClass.set(entrypoint) + // classpath is wired below in `afterEvaluate`, once core/build.gradle.kts has registered + // `nativeImageJar` and `jar`. + + buildArgs( + buildList { + // If PGO flags are present, add them first; if not, add `-Ox`. + when (pgoArgs.isEmpty()) { + true -> add("-O$nativeOpt") + false -> addAll(pgoArgs) + } + + // Common flags for Native Image. + addAll( + buildList { + add("-march=$nativeTarget") + if (nativeDebug) { + add("-g") + add("-H:+SourceLevelDebug") + } + // -- + add("--no-fallback") // fail rather than fall back to a JVM-requiring image + add("--gc=$nativeGc") + add("--future-defaults=all") + add("--link-at-build-time=com.facebook") + add("--initialize-at-build-time=com.facebook") + add("--add-opens=java.base/java.util=ALL-UNNAMED") + add("--color=always") + // -- ▼ SVM Hosted Options + add("-H:+ReportExceptionStackTraces") + // a short-lived CLI doesn't need cgroup-based heap sizing + add("-H:-UseContainerSupport") + // -- ▼ SVM Runtime Options + add("-R:+InstallSegfaultHandler") + // -- ▼ Experimental Options + add("-H:+UnlockExperimentalVMOptions") + add("-H:-ReduceImplicitExceptionStackTraceInformation") + add("-H:-UnlockExperimentalVMOptions") + // -- ▼ VM flags + add("-J--enable-native-access=ALL-UNNAMED") + add("-J--illegal-native-access=allow") + add("-J--sun-misc-unsafe-memory-access=allow") + // -- ▼ C Compiler / Linker Flags + if (enableLto) { + add("--native-compiler-options=-flto") + add("-H:NativeLinkerOption=-flto") + } + if (preferMusl) { + add("-H:NativeLinkerOption=-L${muslSysroot}/lib") + } + } + ) + + // Mark what should be initialized at build-time, i.e. persisted to the heap image. + // See `src/main/native-image/initialize-at-build-time.txt` for a list of such classes. + addLinesFromFile("src", "main", "native-image", "initialize-at-build-time.txt") { + "--initialize-at-build-time=$it" + } + + // Still other classes must be initialized at runtime only. + // See `src/main/native-image/initialize-at-run-time.txt` for a list of such classes. + addLinesFromFile("src", "main", "native-image", "initialize-at-run-time.txt") { + "--initialize-at-run-time=$it" + } + + // Here, we prefer static linking, for startup performance and release simplicity. + // On Linux amd64, we target musl to avoid linking conflicts with older glibc. + // On macOS, pass `--static-nolibc` for the closest option available. + when (System.getProperty("os.name")) { + "Linux" -> + when (System.getProperty("os.arch")) { + "amd64" -> + when (preferMusl) { + true -> addAll(listOf("--static", "--libc=musl", "-H:+StaticLibStdCpp")) + false -> add("--static-nolibc") + } + else -> add("--static-nolibc") + } + "Mac OS X" -> add("--static-nolibc") + } + } + ) + } + } +} + +// The image classpath includes `nativeImageJar` and `jar`, which core/build.gradle.kts registers +// after this plugin is applied. Wire them in once the project is evaluated, using their archiveFile +// providers so the producing tasks are tracked as dependencies (otherwise downstream GraalVM tasks +// such as `generateResourcesConfigFile` fail Gradle's implicit-dependency validation). +afterEvaluate { + configure { + binaries { + named("main") { + classpath.from( + tasks.named("nativeImageJar").flatMap { it.archiveFile }, + tasks.named("jar").flatMap { it.archiveFile }, + configurations.named("compileClasspath"), + configurations.named("runtimeClasspath"), + configurations.named("nativeImageClasspath"), + ) + } + } + } +} + +fun MutableList.addLinesFromFile(vararg path: String, mapper: (String) -> String) { + file(Paths.get(path.first(), *path.drop(1).toTypedArray()).toString()) + .useLines { lines -> + lines + .map { it.trim() } + // Skip blank lines and `#` comments so these metadata files can be documented. + .filter { it.isNotEmpty() && !it.startsWith("#") } + .map(mapper) + .toList() + } + .also { addAll(it) } +} diff --git a/build.gradle.kts b/build.gradle.kts index 0b5e58cb..edd6cfd6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -21,6 +21,7 @@ plugins { alias(libs.plugins.ktfmt) apply false alias(libs.plugins.nexusPublish) alias(libs.plugins.shadowJar) apply false + alias(libs.plugins.graalvm) apply false } version = providers.gradleProperty("ktfmt.version").get() diff --git a/core/README.md b/core/README.md new file mode 100644 index 00000000..1bb54417 --- /dev/null +++ b/core/README.md @@ -0,0 +1,57 @@ +# Core + +This is the core ktfmt CLI project. + +## Native Image metadata + +GraalVM `native-image` configuration for the `ktfmt` binary. These files are read by the build +(`build-logic/src/main/kotlin/ktfmt.native-image.gradle.kts`) and the `nativeImage` source set. + +### Files + +These live under `core/src/main/native-image/`: + +- `kotlin/.../KotlinCoreEnvironmentCompanion.kt`: SVM substitution that no-ops + `KotlinCoreEnvironment.Companion.registerApplicationExtensionPointsAndExtensionsFrom`, which is + incompatible with SVM and unneeded. Compiled into the image; never loaded on the JVM. +- `resources/META-INF/native-image/.../reachability-metadata.json`: reflection/resource/proxy + metadata generated by the GraalVM tracing agent (treated as generated; see `.gitattributes`). +- `initialize-at-build-time.txt` and `initialize-at-run-time.txt`: one class or package per line + (blank lines and `#` comments ignored). They control which classes are initialized into the + build-time image heap versus at runtime. +- `profiles/`: PGO profiles (gitignored). Generate `default.iprof` with `bash pgo_train.sh`; it is + used when `-Pktfmt.native.pgo=true` is passed to `:ktfmt:nativeCompile`. + +### Upgrading the Kotlin compiler (or other dependencies) + +GraalVM compiles the binary ahead of time, so it has to know in advance every class ktfmt reflects +on. That information lives in the files above, and it can go out of date when +`kotlin-compiler-embeddable` is bumped. You will notice when the CI "Native Image" job fails, or +`./native_smoke_test.sh` crashes locally. There are two cases. Run the commands below from the repo +root. + +**1. The binary builds but crashes while formatting** (a runtime `NoSuchMethodException`, often on +`org.jetbrains.kotlin.psi.Kt*.(...ASTNode)`). The reflection metadata is stale. Regenerate it: + +``` +./regenerate_native_metadata.sh +``` + +That script builds the formatter, re-traces the reflection it performs, and updates +`reachability-metadata.json`. You do not need to know GraalVM; if a GraalVM JDK is missing, the +script tells you how to install one. Commit the updated file. + +**2. The native build itself fails** with ` was found in the image heap ... marked for +initialization at image run time`. A class initialized at build time pulled in a type that can only +be initialized at runtime. Fix it by editing the plain-text lists under `core/src/main/native-image/`: + +- add the named type to `initialize-at-build-time.txt`, or... +- if it is a `com.facebook.*` class whose static initializer builds a runtime-only object graph (as + `EditorConfigResolver` and `CompatibilityUtilsKt` do), add it to `initialize-at-run-time.txt` + instead. + +After either fix, rebuild and confirm: + +``` +./gradlew :ktfmt:nativeCompile && ./native_smoke_test.sh +``` diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 50985360..242ae536 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -17,12 +17,16 @@ import kotlin.io.path.writeText import org.jetbrains.intellij.platform.gradle.utils.asPath import org.jetbrains.kotlin.gradle.dsl.abi.ExperimentalAbiValidation +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { kotlin("jvm") id("com.gradleup.shadow") id("com.ncorti.ktfmt.gradle") id("maven-publish") + // Applies the GraalVM Native Image plugin and configures the `ktfmt` native binary. See + // build-logic/src/main/kotlin/ktfmt.native-image.gradle.kts. + id("ktfmt.native-image") id("org.jetbrains.dokka") id("signing") } @@ -32,6 +36,14 @@ repositories { mavenCentral() } +// Resolvable classpath for compiling the Native Image substitution: the main compile deps (for the +// kotlinc types it targets) plus GraalVM's `svm`. Kept standalone so `svm` does NOT leak into the +// `nativeImage` source set's configurations (and from there into the GraalVM image classpath). +val nativeImageHelperClasspath by configurations.creating { + extendsFrom(configurations.implementation.get()) + isCanBeResolved = true +} + dependencies { api(libs.googleJavaformat) api(libs.guava) @@ -42,6 +54,14 @@ dependencies { testImplementation(libs.kotlin.test.junit4) testImplementation(libs.googleTruth) testImplementation(libs.junit) + + nativeImageHelperClasspath(libs.graalvm.nativeimage) + // `nativeImageClasspath` is created by the GraalVM plugin (applied via `ktfmt.native-image`), so + // reference it by name rather than via a generated accessor. + "nativeImageClasspath"(libs.jline.terminal) + "nativeImageClasspath"(libs.jline.terminal.jansi) + "nativeImageClasspath"(libs.jline.terminal.jna) + "nativeImageClasspath"(libs.jline.terminal.jni) } val generateSources by tasks.registering { @@ -52,28 +72,44 @@ val generateSources by tasks.registering { tasks { // Create Ktfmt.kt file with version information register("generateKtfmtFile") { - val genVersionFileScript = rootProject.rootDir.resolve("gen_version_file.sh") - val versionPropertiesFile = rootProject.rootDir.resolve("gradle.properties") + val version = providers.gradleProperty("ktfmt.version") val versionFile = layout.buildDirectory.file("generated/main/java/com/facebook/ktfmt/util/Ktfmt.kt") - inputs.files(genVersionFileScript, versionPropertiesFile) + inputs.property("version", version) outputs.file(versionFile) outputs.cacheIf { true } - // provider to run the shell script genVersionFileScript with versionPropertiesFile as argument - val scriptProcess = providers.exec { - workingDir = rootProject.rootDir - commandLine = listOf(genVersionFileScript.toString(), versionPropertiesFile.toString()) - } - doLast { - val scriptOutput = scriptProcess.standardOutput.asText.get() - if (scriptProcess.result.get().exitValue != 0) { - val scriptError = scriptProcess.standardError.asText.get() - error("Failed to generate version file!\nstdout:\n$scriptOutput\n\nstderr:\n$scriptError") - } - versionFile.get().asPath.writeText(scriptOutput) + versionFile + .get() + .asPath + .writeText( + """ + /* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ + + package com.facebook.ktfmt.util + + object Ktfmt { + const val version = "${version.get()}" + } + """ + .trimIndent() + "\n" + ) logger.info("Generated version file at ${versionFile.get()}") } } @@ -82,11 +118,13 @@ tasks { test { jvmArgs("-Dfile.encoding=UTF-16") } // Handle multiple versions of Kotlin here - withType { + withType { // Only get major and minor version, e.g. 1.8.0-beta1 -> 1.8 val kotlinVersion = rootProject.libs.versions.kotlin.get().substringBeforeLast(".") exclude { - val path = it.file.path + // it.path is tree-relative and always '/'-separated, unlike it.file.path which uses '\' on + // Windows (where the old check matched nothing and compiled every kotlin-x.y shim at once). + val path = it.path "com/facebook/ktfmt/util/kotlin-" in path && "kotlin-$kotlinVersion" !in path } } @@ -116,6 +154,25 @@ tasks { } } +// The Native Image substitution helper + reachability metadata live in their own source set so the +// GraalVM-only `svm` dependency stays out of the main/published artifact. The Kotlin source is +// compiled by the auto-created `compileNativeImageKotlin` task (kotlin srcDir configured below). +sourceSets { create("nativeImage") { resources { srcDir("src/main/native-image/resources") } } } + +// Native Image artifacts jar (local only, not published) +val nativeImageJar by + tasks.registering(Jar::class) { + group = "build" + description = "Assembles Native Image jar and resources" + from(sourceSets["nativeImage"].output) + archiveClassifier = "nativeimage" + } + +// Compile the substitution against `svm` without putting it on the source set's configurations. +tasks.named("compileNativeImageKotlin") { + libraries.from(nativeImageHelperClasspath) +} + kotlin { @OptIn(ExperimentalAbiValidation::class) abiValidation { enabled = true } @@ -129,6 +186,7 @@ kotlin { srcDir(generateSources) } } + named("nativeImage") { kotlin { srcDir("src/main/native-image/kotlin") } } } } diff --git a/core/src/main/java/com/facebook/ktfmt/format/Parser.kt b/core/src/main/java/com/facebook/ktfmt/format/Parser.kt index 799f2c06..850574bc 100644 --- a/core/src/main/java/com/facebook/ktfmt/format/Parser.kt +++ b/core/src/main/java/com/facebook/ktfmt/format/Parser.kt @@ -43,9 +43,7 @@ object Parser { * from [KotlinCoreEnvironment.createForProduction]: * https://github.com/JetBrains/kotlin/blob/master/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt#L544 */ - val env: KotlinCoreEnvironment - - init { + val env: KotlinCoreEnvironment by lazy { // To hide annoying warning on Windows System.setProperty("idea.use.native.fs.for.win", "false") val disposable = Disposer.newDisposable() @@ -54,13 +52,12 @@ object Parser { CommonConfigurationKeys.MESSAGE_COLLECTOR_KEY, PrintingMessageCollector(System.err, PLAIN_RELATIVE_PATHS, false), ) - env = - @Suppress("OPT_IN_USAGE_ERROR") // KotlinCoreEnvironment.createForProduction - KotlinCoreEnvironment.createForProduction( - disposable, - configuration, - EnvironmentConfigFiles.JVM_CONFIG_FILES, - ) + @Suppress("OPT_IN_USAGE_ERROR") // KotlinCoreEnvironment.createForProduction + KotlinCoreEnvironment.createForProduction( + disposable, + configuration, + EnvironmentConfigFiles.JVM_CONFIG_FILES, + ) } fun parse(code: String): KtFile { diff --git a/core/src/main/native-image/initialize-at-build-time.txt b/core/src/main/native-image/initialize-at-build-time.txt new file mode 100644 index 00000000..b58cc56e --- /dev/null +++ b/core/src/main/native-image/initialize-at-build-time.txt @@ -0,0 +1,10 @@ +com.google.common.collect.SingletonImmutableList +kotlin.Function +kotlin.KotlinVersion +kotlin.SynchronizedLazyImpl +kotlin.UNINITIALIZED_VALUE +kotlin.collections.AbstractList$Companion +kotlin.collections.EmptyMap +kotlin.enums.EnumEntriesList +kotlin.jvm.functions.Function1 +kotlin.text.Regex diff --git a/core/src/main/native-image/initialize-at-run-time.txt b/core/src/main/native-image/initialize-at-run-time.txt new file mode 100644 index 00000000..a7d9b4fc --- /dev/null +++ b/core/src/main/native-image/initialize-at-run-time.txt @@ -0,0 +1,12 @@ +# Some ktfmt classes build run-time-only object graphs in their (ec4j config types, Kotlin +# compiler PSI stub element types); keep them out of the build-time image heap. The rest of +# com.facebook is initialized at build time for startup speed. +com.facebook.ktfmt.cli.EditorConfigResolver +com.facebook.ktfmt.util.CompatibilityUtilsKt +kotlin.random.AbstractPlatformRandom +kotlin.random.Random +kotlin.random.Random$Default +kotlin.random.RandomKt +kotlin.random.XorWowRandom +kotlin.random.jdk8.PlatformThreadLocalRandom +kotlin.uuid.SecureRandomHolder diff --git a/core/src/main/native-image/kotlin/com/facebook/ktfmt/nativeImage/KotlinCoreEnvironmentCompanion.kt b/core/src/main/native-image/kotlin/com/facebook/ktfmt/nativeImage/KotlinCoreEnvironmentCompanion.kt new file mode 100644 index 00000000..dc6507a1 --- /dev/null +++ b/core/src/main/native-image/kotlin/com/facebook/ktfmt/nativeImage/KotlinCoreEnvironmentCompanion.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ + +package com.facebook.ktfmt.nativeImage + +import com.oracle.svm.core.annotate.Substitute +import com.oracle.svm.core.annotate.TargetClass +import com.oracle.svm.core.annotate.TargetElement +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.config.CompilerConfiguration + +/** + * Substitution that lets `kotlinc`'s infrastructure work under SVM by skipping + * `registerApplicationExtensionPointsAndExtensionsFrom`, which is incompatible and not needed + * anyway. + * + * Only used by the Native Image build; never loaded on the JVM. + */ +@Suppress("unused") +@TargetClass(KotlinCoreEnvironment.Companion::class) +internal class KotlinCoreEnvironmentCompanion { + @Substitute + @TargetElement(name = "registerApplicationExtensionPointsAndExtensionsFrom") + private fun stubbedRegisterApplicationExtensionPointsAndExtensionsFrom( + configuration: CompilerConfiguration, + configFilePath: String, + ) { + // Nothing at this time. + } +} diff --git a/core/src/main/native-image/resources/META-INF/native-image/com/facebook/ktfmt/reachability-metadata.json b/core/src/main/native-image/resources/META-INF/native-image/com/facebook/ktfmt/reachability-metadata.json new file mode 100644 index 00000000..8bc3bd1b --- /dev/null +++ b/core/src/main/native-image/resources/META-INF/native-image/com/facebook/ktfmt/reachability-metadata.json @@ -0,0 +1,2247 @@ +{ + "reflection": [ + { + "type": "android.os.Build$VERSION" + }, + { + "type": "java.util.concurrent.ForkJoinTask", + "fields": [ + { + "name": "aux" + }, + { + "name": "status" + } + ] + }, + { + "type": "java.util.concurrent.atomic.AtomicBoolean", + "fields": [ + { + "name": "value" + } + ] + }, + { + "type": "java.util.concurrent.atomic.AtomicReference", + "fields": [ + { + "name": "value" + } + ] + }, + { + "type": "kotlin.SafePublicationLazyImpl", + "fields": [ + { + "name": "_value" + } + ] + }, + { + "type": "kotlin.jvm.internal.DefaultConstructorMarker" + }, + { + "type": "kotlin.reflect.jvm.internal.ReflectionFactoryImpl", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "kotlinx.coroutines.CancellableContinuationImpl" + }, + { + "type": "kotlinx.coroutines.EventLoopImplBase", + "fields": [ + { + "name": "_delayed$volatile" + }, + { + "name": "_isCompleted$volatile" + }, + { + "name": "_queue$volatile" + } + ] + }, + { + "type": "kotlinx.coroutines.JobSupport", + "fields": [ + { + "name": "_parentHandle$volatile" + }, + { + "name": "_state$volatile" + } + ] + }, + { + "type": "kotlinx.coroutines.JobSupport$Finishing" + }, + { + "type": "kotlinx.coroutines.channels.BufferedChannel" + }, + { + "type": "kotlinx.coroutines.internal.AtomicOp" + }, + { + "type": "kotlinx.coroutines.internal.ConcurrentLinkedListNode" + }, + { + "type": "kotlinx.coroutines.internal.DispatchedContinuation", + "fields": [ + { + "name": "_reusableCancellableContinuation$volatile" + } + ] + }, + { + "type": "kotlinx.coroutines.internal.LimitedDispatcher", + "fields": [ + { + "name": "runningWorkers$volatile" + } + ] + }, + { + "type": "kotlinx.coroutines.internal.LockFreeLinkedListNode" + }, + { + "type": "kotlinx.coroutines.internal.LockFreeTaskQueue", + "fields": [ + { + "name": "_cur$volatile" + } + ] + }, + { + "type": "kotlinx.coroutines.internal.LockFreeTaskQueueCore", + "fields": [ + { + "name": "_next$volatile" + }, + { + "name": "_state$volatile" + } + ] + }, + { + "type": "kotlinx.coroutines.internal.Segment" + }, + { + "type": "kotlinx.coroutines.scheduling.CoroutineScheduler", + "fields": [ + { + "name": "_isTerminated$volatile" + }, + { + "name": "controlState$volatile" + }, + { + "name": "parkedWorkersStack$volatile" + } + ] + }, + { + "type": "kotlinx.coroutines.scheduling.CoroutineScheduler$Worker" + }, + { + "type": "kotlinx.coroutines.scheduling.WorkQueue" + }, + { + "type": "org.jetbrains.kotlin.cli.common.CompilerSystemProperties" + }, + { + "type": "org.jetbrains.kotlin.com.intellij.codeInsight.ContainerProvider" + }, + { + "type": "org.jetbrains.kotlin.com.intellij.codeInsight.multiverse.CodeInsightContextProvider" + }, + { + "type": "org.jetbrains.kotlin.com.intellij.codeInsight.multiverse.MultiverseEnabler" + }, + { + "type": "org.jetbrains.kotlin.com.intellij.openapi.application.impl.ModalityStateEx", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.jetbrains.kotlin.com.intellij.openapi.extensions.impl.ExtensionPointImpl", + "fields": [ + { + "name": "keyMapperToCache" + }, + { + "name": "listeners" + } + ] + }, + { + "type": "org.jetbrains.kotlin.com.intellij.openapi.fileTypes.BinaryFileTypeDecompilers", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.jetbrains.kotlin.com.intellij.openapi.fileTypes.LanguageFileType", + "methods": [ + { + "name": "extractCharsetFromFileContent", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.openapi.project.Project", + "org.jetbrains.kotlin.com.intellij.openapi.vfs.VirtualFile", + "java.lang.CharSequence" + ] + }, + { + "name": "extractCharsetFromFileContent", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.openapi.project.Project", + "org.jetbrains.kotlin.com.intellij.openapi.vfs.VirtualFile", + "java.lang.String" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.com.intellij.openapi.util.SimpleModificationTracker", + "fields": [ + { + "name": "myCounter" + } + ] + }, + { + "type": "org.jetbrains.kotlin.com.intellij.openapi.vfs.PersistentFSConstants", + "fields": [ + { + "name": "ourMaxIntellisenseFileSize" + } + ] + }, + { + "type": "org.jetbrains.kotlin.com.intellij.psi.LanguageSubstitutors", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.jetbrains.kotlin.com.intellij.psi.PsiElementFinder" + }, + { + "type": "org.jetbrains.kotlin.com.intellij.psi.SingleRootFileViewProvider", + "fields": [ + { + "name": "myPsiFile" + } + ] + }, + { + "type": "org.jetbrains.kotlin.com.intellij.psi.compiled.ClassFileDecompilers", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.jetbrains.kotlin.com.intellij.psi.compiled.ClassFileDecompilers$Decompiler" + }, + { + "type": "org.jetbrains.kotlin.com.intellij.psi.impl.source.JavaFileElementType", + "methods": [ + { + "name": "getExternalId", + "parameterTypes": [] + } + ] + }, + { + "type": "org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.CompositeElement", + "fields": [ + { + "name": "myWrapper" + } + ] + }, + { + "type": "org.jetbrains.kotlin.com.intellij.psi.stubs.StubBase", + "fields": [ + { + "name": "myPsi" + } + ] + }, + { + "type": "org.jetbrains.kotlin.com.intellij.util.ConcurrentLongObjectHashMap", + "fields": [ + { + "name": "baseCount" + }, + { + "name": "cellsBusy" + }, + { + "name": "sizeCtl" + }, + { + "name": "transferIndex" + } + ] + }, + { + "type": "org.jetbrains.kotlin.com.intellij.util.ConcurrentLongObjectHashMap$CounterCell", + "fields": [ + { + "name": "value" + } + ] + }, + { + "type": "org.jetbrains.kotlin.com.intellij.util.KeyedLazyInstanceEP" + }, + { + "type": "org.jetbrains.kotlin.com.intellij.util.QueryExecutor" + }, + { + "type": "org.jetbrains.kotlin.extensions.CompilerConfigurationExtension" + }, + { + "type": "org.jetbrains.kotlin.extensions.CompilerConfigurationExtension[]" + }, + { + "type": "org.jetbrains.kotlin.idea.KotlinFileType" + }, + { + "type": "org.jetbrains.kotlin.kdoc.psi.impl.KDocLink[]" + }, + { + "type": "org.jetbrains.kotlin.kdoc.psi.impl.KDocName", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.kdoc.psi.impl.KDocName[]" + }, + { + "type": "org.jetbrains.kotlin.kdoc.psi.impl.KDocSection", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.kdoc.psi.impl.KDocSection[]" + }, + { + "type": "org.jetbrains.kotlin.kdoc.psi.impl.KDocTag", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.kdoc.psi.impl.KDocTag[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtAnnotatedExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtAnnotation", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtAnnotationEntry", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinAnnotationEntryStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtAnnotationEntry[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtAnnotationUseSiteTarget", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinAnnotationUseSiteTargetStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtAnnotationUseSiteTarget[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtAnnotation[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtArrayAccessExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtBackingField", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinBackingFieldStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtBackingField[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtBinaryExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtBinaryExpressionWithTypeRHS", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtBlockStringTemplateEntry", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinBlockStringTemplateEntryStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtBlockStringTemplateEntry[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtBreakExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtCallExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtCallExpression[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtCallableReferenceExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtCatchClause", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtClass", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinClassStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtClassBody", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtClassBody[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtClassInitializer", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtClassInitializer[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtClassLiteralExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinClassLiteralExpressionStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtClassLiteralExpression[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtClass[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtCollectionLiteralExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinCollectionLiteralExpressionStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtCollectionLiteralExpression[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtConstantExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinConstantExpressionStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtConstantExpression[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtConstructorCalleeExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtConstructorCalleeExpression[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtConstructorDelegationCall", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtConstructorDelegationReferenceExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtContainerNode", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtContainerNodeForControlStructureBody", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtContextReceiver", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinContextReceiverStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtContextReceiverList", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtContextReceiverList[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtContextReceiver[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtContinueExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtContractEffect", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinContractEffectStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtContractEffectList", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtContractEffectList[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtContractEffect[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtDeclarationModifierList", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinModifierListStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtDeclarationModifierList[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtDelegatedSuperTypeEntry", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtDelegatedSuperTypeEntry[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtDestructuringDeclaration", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtDestructuringDeclarationEntry", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtDoWhileExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtDotQualifiedExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtDotQualifiedExpression[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtDynamicType", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtDynamicType[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtEnumEntry", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtEnumEntrySuperclassReferenceExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinEnumEntrySuperclassReferenceExpressionStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtEnumEntrySuperclassReferenceExpression[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtEnumEntry[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtEscapeStringTemplateEntry", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderWithTextStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtEscapeStringTemplateEntry[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtFileAnnotationList", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtFileAnnotationList[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtFinallySection", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtForExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtFunctionLiteral", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtFunctionType", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinFunctionTypeStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtFunctionTypeReceiver", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtFunctionTypeReceiver[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtFunctionType[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtIfExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtImportAlias", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinImportAliasStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtImportAlias[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtImportDirective", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinImportDirectiveStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtImportDirective[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtImportList", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtImportList[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtInitializerList", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtInitializerList[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtIntersectionType", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtIntersectionType[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtIsExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtLabelReferenceExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtLabeledExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtLambdaArgument", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinValueArgumentStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtLambdaArgument[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtLiteralStringTemplateEntry", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderWithTextStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtLiteralStringTemplateEntry[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtNameReferenceExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinNameReferenceExpressionStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtNameReferenceExpression[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtNamedFunction", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinFunctionStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtNamedFunction[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtNullableType", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtNullableType[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtObjectDeclaration", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinObjectStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtObjectDeclaration[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtObjectLiteralExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtOperationReferenceExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtPackageDirective", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtPackageDirective[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtParameter", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinParameterStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtParameterList", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtParameterList[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtParameter[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtParenthesizedExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtPostfixExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtPrefixExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtPrimaryConstructor", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinConstructorStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtPrimaryConstructor[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtProperty", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinPropertyStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtPropertyAccessor", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinPropertyAccessorStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtPropertyAccessor[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtPropertyDelegate", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtProperty[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtReturnExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtSafeQualifiedExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtScript", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinScriptStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtScriptInitializer", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtScriptInitializer[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtScript[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtSecondaryConstructor", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinConstructorStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtSecondaryConstructor[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtSimpleNameStringTemplateEntry", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderWithTextStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtSimpleNameStringTemplateEntry[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtStringInterpolationPrefix", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinStringInterpolationPrefixStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtStringInterpolationPrefix[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtStringTemplateExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtStringTemplateExpression[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtSuperExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtSuperTypeCallEntry", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtSuperTypeCallEntry[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtSuperTypeEntry", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtSuperTypeEntry[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtSuperTypeList", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtSuperTypeList[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtThisExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtThrowExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtTryExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtTypeAlias", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinTypeAliasStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtTypeAlias[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtTypeArgumentList", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtTypeArgumentList[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtTypeConstraint", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtTypeConstraintList", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtTypeConstraintList[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtTypeConstraint[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtTypeParameter", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinTypeParameterStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtTypeParameterList", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtTypeParameterList[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtTypeParameter[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtTypeProjection", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinTypeProjectionStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtTypeProjection[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtTypeReference", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtTypeReference[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtUserType", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinUserTypeStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtUserType[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtValueArgument", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinValueArgumentStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtValueArgumentList", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtValueArgumentList[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtValueArgumentName", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + }, + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtValueArgumentName[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtValueArgument[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtWhenConditionInRange", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtWhenConditionIsPattern", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtWhenConditionWithExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtWhenCondition[]" + }, + { + "type": "org.jetbrains.kotlin.psi.KtWhenEntry", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtWhenEntryGuard", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtWhenExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.KtWhileExpression", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jetbrains.kotlin.com.intellij.lang.ASTNode" + ] + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.impl.KotlinElementTypeProviderImpl", + "fields": [ + { + "name": "INSTANCE" + } + ] + }, + { + "type": "org.jetbrains.kotlin.psi.stubs.elements.KtFileElementType", + "methods": [ + { + "name": "getExternalId", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.misc.Unsafe", + "allDeclaredFields": true, + "methods": [ + { + "name": "arrayBaseOffset", + "parameterTypes": [ + "java.lang.Class" + ] + }, + { + "name": "arrayIndexScale", + "parameterTypes": [ + "java.lang.Class" + ] + }, + { + "name": "compareAndSwapInt", + "parameterTypes": [ + "java.lang.Object", + "long", + "int", + "int" + ] + }, + { + "name": "compareAndSwapLong", + "parameterTypes": [ + "java.lang.Object", + "long", + "long", + "long" + ] + }, + { + "name": "compareAndSwapObject", + "parameterTypes": [ + "java.lang.Object", + "long", + "java.lang.Object", + "java.lang.Object" + ] + }, + { + "name": "copyMemory", + "parameterTypes": [ + "java.lang.Object", + "long", + "java.lang.Object", + "long", + "long" + ] + }, + { + "name": "getAndAddInt", + "parameterTypes": [ + "java.lang.Object", + "long", + "int" + ] + }, + { + "name": "getObjectVolatile", + "parameterTypes": [ + "java.lang.Object", + "long" + ] + }, + { + "name": "invokeCleaner", + "parameterTypes": [ + "java.nio.ByteBuffer" + ] + }, + { + "name": "objectFieldOffset", + "parameterTypes": [ + "java.lang.reflect.Field" + ] + }, + { + "name": "putObjectVolatile", + "parameterTypes": [ + "java.lang.Object", + "long", + "java.lang.Object" + ] + } + ] + }, + { + "type": { + "proxy": [ + "org.jetbrains.kotlin.com.intellij.openapi.command.CommandListener" + ] + } + }, + { + "type": { + "proxy": [ + "org.jetbrains.kotlin.com.intellij.openapi.vfs.VirtualFileListener" + ] + } + }, + { + "type": { + "proxy": [ + "org.jetbrains.kotlin.com.intellij.psi.util.PsiModificationTracker$Listener" + ] + } + } + ], + "resources": [ + { + "glob": "org/jetbrains/kotlin/cli/common/CompilerSystemProperties.class" + }, + { + "glob": "pluginsCompatibleWithK2Mode.txt" + } + ] +} \ No newline at end of file diff --git a/gen_version_file.sh b/gen_version_file.sh deleted file mode 100755 index a9805955..00000000 --- a/gen_version_file.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/sh -# Copyright (c) Meta Platforms, Inc. and affiliates. -# -# 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. - -# @licenselint-loose-mode - -set -e - -# Absolute path to this script, e.g. /home/user/ktfmt/smoke_tests.sh -SCRIPT=$(readlink -f "$0") -# Absolute path this script is in, thus /home/user/ktfmt/ -ROOT_DIR=$(dirname "$SCRIPT") - -VERSION_FILE="${1:-"$ROOT_DIR/gradle.properties"}" - -VERSION=$(grep "ktfmt.version" "$VERSION_FILE" | cut -f 2 -d "=") - -echo "\ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * 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. - */ - -package com.facebook.ktfmt.util - -object Ktfmt { - const val version = \"$VERSION\" -}" diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f6bfbd66..82759b14 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,10 @@ com-google-code-gson-gson = "2.10.1" com-google-googlejavaformat-google-java-format = "1.23.0" com-google-guava-guava = "33.5.0-jre" com-google-truth-truth = "1.0" +org-graalvm = "25.0.1" +graalvmPlugin = "0.11.1" junit-junit = "4.13.1" +org-jline = "3.30.6" net-java-dev-jna-jna = "4.2.2" org-ec4j-core = "1.2.0" @@ -27,7 +30,12 @@ ec4j = { module = "org.ec4j.core:ec4j-core", version.ref = "org-ec4j-core" } googleJavaformat = { module = "com.google.googlejavaformat:google-java-format", version.ref = "com-google-googlejavaformat-google-java-format" } googleTruth = { module = "com.google.truth:truth", version.ref = "com-google-truth-truth" } gson = { module = "com.google.code.gson:gson", version.ref = "com-google-code-gson-gson" } +graalvm-nativeimage = { module = "org.graalvm.nativeimage:svm", version.ref ="org-graalvm" } guava = { module = "com.google.guava:guava", version.ref = "com-google-guava-guava" } +jline-terminal = { module = "org.jline:jline-terminal", version.ref = "org-jline" } +jline-terminal-jansi = { module = "org.jline:jline-terminal-jansi", version.ref = "org-jline" } +jline-terminal-jna = { module = "org.jline:jline-terminal-jna", version.ref = "org-jline" } +jline-terminal-jni = { module = "org.jline:jline-terminal-jni", version.ref = "org-jline" } junit = { module = "junit:junit", version.ref = "junit-junit" } jna = { module = "net.java.dev.jna:jna", version.ref = "net-java-dev-jna-jna" } ktfmt = { module = "com.facebook:ktfmt", version.ref = "ktfmt" } @@ -37,6 +45,7 @@ kotlin-test-junit4 = { module = "org.jetbrains.kotlin:kotlin-test-junit", versio [plugins] dokka = { id = "org.jetbrains.dokka", version.ref = "gradlePlugin-dokka" } +graalvm = { id = "org.graalvm.buildtools.native", version.ref = "graalvmPlugin" } intelliJPlatform = { id = "org.jetbrains.intellij.platform", version.ref = "gradlePlugin-intelliJPlatform" } kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } ktfmt = { id = "com.ncorti.ktfmt.gradle", version.ref = "gradlePlugin-ktfmt" } diff --git a/native_smoke_test.sh b/native_smoke_test.sh new file mode 100755 index 00000000..126619af --- /dev/null +++ b/native_smoke_test.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# 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. + +# Smoke-tests a ktfmt native binary: confirms it runs and actually formats Kotlin without crashing. +# This exercises the reflection-heavy parser/formatter path that a bare `--version` check would miss +# (e.g. stale GraalVM reachability metadata). +# +# Run from the repo root. +# Usage: ./native_smoke_test.sh [path-to-ktfmt-binary] +set -euo pipefail + +bin="${1:-./core/build/native/nativeCompile/ktfmt}" +if [[ ! -e "$bin" && -e "${bin}.exe" ]]; then + bin="${bin}.exe" +fi + +# A format crash is almost always stale GraalVM reachability metadata. Point at the fix. +stale_hint() { + echo "native ktfmt crashed while formatting; the GraalVM reachability metadata is likely stale." >&2 + echo "Regenerate with ./regenerate_native_metadata.sh (see core/README.md)." >&2 + exit 1 +} + +"$bin" --version + +# Representative constructs (enum/class/lambda) that drive Kotlin-compiler PSI reflection. +sample=$'enum class E { A, B }\nclass Foo(val a: Int) { fun bar() = listOf(1, 2).map { it * 2 } }' +formatted="$(printf '%s\n' "$sample" | "$bin" -)" || stale_hint +printf '%s\n' "$formatted" +[[ -n "$formatted" ]] || stale_hint + +# Idempotency: re-formatting the output must be a no-op. +reformatted="$(printf '%s\n' "$formatted" | "$bin" -)" || stale_hint +[[ "$formatted" == "$reformatted" ]] || { + echo 'native ktfmt output is not idempotent' >&2 + exit 1 +} + +# Broader coverage: formatting ktfmt's own sources drives many more Kotlin constructs, and thus +# reflective paths, than the snippet above. Skipped when the sources aren't present. +if [[ -d core/src/main ]]; then + "$bin" -n core/src/main core/src/test >/dev/null || stale_hint +fi + +echo "Native smoke test passed." diff --git a/pgo_train.sh b/pgo_train.sh new file mode 100644 index 00000000..4f2b16f8 --- /dev/null +++ b/pgo_train.sh @@ -0,0 +1,49 @@ +#!/bin/sh +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# 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. + +set -e + +# Run from the repo root. + +# clean any current native build +./gradlew :ktfmt:clean; + +# build natively with PGO training on +./gradlew :ktfmt:nativeCompile \ + -Pktfmt.native.pgo=train \ + -Pktfmt.native.target=native \ + -Pktfmt.native.lto=true; + +echo "PGO training starting" + +./core/build/native/nativeCompile/ktfmt -n $PWD + +echo "PGO training completed." + +mkdir -p ./core/src/main/native-image/profiles +cp -fv ./default.iprof ./core/src/main/native-image/profiles/default.iprof; + +echo "Rebuilding..." + +# clean for PGO-trained build +./gradlew :ktfmt:clean; + +# rebuild +./gradlew :ktfmt:nativeCompile \ + -Pktfmt.native.release=true \ + -Pktfmt.native.lto=true \ + -Pktfmt.native.pgo=true; + +echo "PGO-trained build complete." diff --git a/regenerate_native_metadata.sh b/regenerate_native_metadata.sh new file mode 100755 index 00000000..31ef7df1 --- /dev/null +++ b/regenerate_native_metadata.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# 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. + +# Regenerates the GraalVM native-image reachability metadata for the ktfmt binary +# (core/src/main/native-image/resources/...). Run this from the repo root when CI reports the +# metadata is stale, which usually happens after a kotlin-compiler-embeddable upgrade. See +# core/README.md. +# +# You do not need to know GraalVM to run this. It only needs a GraalVM JDK (which bundles the +# tracing agent); if one is missing it tells you how to install it. +set -euo pipefail + +meta_dir="core/src/main/native-image/resources/META-INF/native-image/com/facebook/ktfmt" + +# 1. Find a GraalVM `java` (it bundles the native-image tracing agent). +if [[ -n "${GRAALVM_HOME:-}" && -x "${GRAALVM_HOME}/bin/java" ]]; then + java_bin="${GRAALVM_HOME}/bin/java" +elif command -v java >/dev/null 2>&1 && java -version 2>&1 | grep -qi graalvm; then + java_bin="java" +else + cat >&2 <<'EOF' +This script needs a GraalVM JDK (for the native-image tracing agent), which was not found. + +Install one, then re-run this script: + - SDKMAN: sdk install java 25-graalce && sdk use java 25-graalce + - Homebrew: brew install --cask graalvm-jdk + - Manual: https://www.graalvm.org/downloads/ (then `export GRAALVM_HOME=`) +EOF + exit 1 +fi +echo "Using GraalVM java: $java_bin" + +# 2. Build the JVM fat jar to trace (uses your normal JDK for Gradle, not necessarily GraalVM). +echo "Building the fat jar..." +./gradlew :ktfmt:shadowJar --no-daemon +jars=(core/build/libs/ktfmt-*-with-dependencies.jar) +jar="${jars[0]}" +[[ -e "$jar" ]] || { + echo "Could not find the fat jar in core/build/libs/" >&2 + exit 1 +} + +# 3. Trace reflection by formatting ktfmt's own sources, merging into the existing metadata. The +# merge happens in a temp dir holding only reachability-metadata.json, so the agent updates just +# that file and can't reintroduce legacy split-config files. `-n` (dry-run) avoids editing sources. +tmp="$(mktemp -d)" +cp "${meta_dir}/reachability-metadata.json" "${tmp}/" +echo "Tracing reflection over core/src..." +"$java_bin" \ + -agentlib:native-image-agent=config-merge-dir="$tmp" \ + -jar "$jar" \ + -n core/src/main core/src/test +cp "${tmp}/reachability-metadata.json" "${meta_dir}/reachability-metadata.json" +rm -rf "$tmp" + +echo +echo "Updated ${meta_dir}/reachability-metadata.json." +echo "Review the diff, then rebuild and smoke-test the native binary:" +echo " ./gradlew :ktfmt:nativeCompile && ./native_smoke_test.sh" diff --git a/settings.gradle.kts b/settings.gradle.kts index ea48f265..fcb8d6c4 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -14,6 +14,8 @@ * limitations under the License. */ +pluginManagement { includeBuild("build-logic") } + rootProject.name = "ktfmt-parent" include(