Development

May 22, 2026 · View on GitHub

Instructions for building the project from source and running it locally against the bundled samples, without going through Maven Central or the VS Code Marketplace.

There are three shipping artifacts, each with its own local install story:

ArtifactBuilt byConsumed via
Gradle plugin (ee.schimke.composeai.preview)includeBuild (in-repo, preferred) or ./gradlew :gradle-plugin:publishToMavenLocalincludeBuild("…/gradle-plugin") or mavenLocal() in a consumer's settings.gradle.kts
CLI (compose-preview)./gradlew :cli:installDistSymlink into ~/.local/bin (or add to $PATH)
VS Code extensioncd vscode-extension && npm install && npm run compileExtension Dev Host (F5), folder symlink, or .vsix

Prerequisites

  • Java 17 (JAVA_HOME set)
  • Gradle wrapper ships with the repo — do not install Gradle separately
  • Node 20+ and npm (for the VS Code extension)
  • VS Code 1.85+ (only if working on the extension)

The bundled samples (samples/android/, samples/cmp/) depend on the gradle-plugin module via includeBuild, so running ./gradlew at the repo root always picks up local plugin changes without any publishing step.

Gradle plugin

Inside this repo, the samples pick up the plugin automatically through the composite build. Nothing to do.

For an external consumer project on the same machine, pick one of the two approaches below.

Have the consumer include this repo's gradle-plugin/ directory directly. No publish step, no cached artifacts to go stale.

// consumer's settings.gradle.kts
pluginManagement {
    repositories {
        gradlePluginPortal()
        google()
        mavenCentral()
    }
    includeBuild("../compose-ai-tools/gradle-plugin")
}
// consumer's <module>/build.gradle.kts
plugins {
    id("ee.schimke.composeai.preview")   // no version — resolved from includeBuild
}

Edits to plugin source rebuild automatically on the next consumer build.

Option B — publishToMavenLocal

Useful when you want to pin a specific built version, or when the consumer can't reach the plugin sources (e.g. Docker build, other machine via rsync).

./gradlew :gradle-plugin:publishToMavenLocal

Then in the consumer's settings.gradle.kts, add mavenLocal() to the plugin repositories:

pluginManagement {
    repositories {
        gradlePluginPortal()
        google()
        mavenCentral()
        mavenLocal()
    }
}

And apply the plugin with whatever version the local build produced:

plugins {
    id("ee.schimke.composeai.preview") version "0.3.4-SNAPSHOT"
}

The version string must match the one in gradle-plugin/build.gradle.kts — the local fallback (used when PLUGIN_VERSION is not set) tracks the next upcoming release (e.g. after tagging v0.3.3, the fallback is 0.3.4-SNAPSHOT).

Smoke test

./gradlew :samples:cmp:composePreviewRenderAll
open samples/cmp/build/compose-previews/renders/

CLI

Build the install image (a native-ish shell launcher + all jars on the classpath):

./gradlew :cli:installDist

Output: cli/build/install/compose-preview/bin/compose-preview.

Wire it onto your $PATH — easiest is a symlink into a directory already on $PATH:

ln -sf "$PWD/cli/build/install/compose-preview/bin/compose-preview" ~/.local/bin/compose-preview

Verify against a sample:

cd samples/cmp
compose-preview list
compose-preview show --filter RedBox

Re-running ./gradlew :cli:installDist after code changes refreshes the launcher — the symlink does not need to be re-created.

VS Code extension

Install dev dependencies and compile TypeScript once:

cd vscode-extension
npm install
npm run compile

Three ways to run the extension locally, in order of preference for day-to-day work:

  1. Open vscode-extension/ as its own VS Code window.
  2. Press F5 (or Run → Start Debugging).
  3. A second VS Code window opens with the extension loaded against the repo root — both samples/android/ and samples/cmp/ are visible, and the module picker at the top of the Compose Preview panel lets you switch between them. Edit TypeScript, press Ctrl+Shift+F5 to reload.

The launch config lives at vscode-extension/.vscode/launch.json with four variants: repo root (default), samples/cmp only, samples/android only, and an empty host. Pick from the Run & Debug dropdown before pressing F5.

For a tighter edit/reload cycle, run npm run watch in a terminal — the compile-on-save will be picked up by the next host reload.

Useful when you want the extension active in your everyday VS Code without running a second window. Make sure out/ is built (npm run compile), then:

ln -sfn "$PWD" ~/.vscode/extensions/schimke.compose-preview-dev

Fully restart VS Code (reloading the window is not always enough on first install). To update, just re-run npm run compile — VS Code picks up the new out/*.js next time it activates the extension.

To uninstall: rm ~/.vscode/extensions/schimke.compose-preview-dev.

Do not also have the Marketplace version installed — VS Code will load both and you'll see duplicate views.

Option 3 — Build and install a .vsix

Closest to what a real user gets:

npm run package
code --install-extension compose-preview-0.3.3.vsix

The package script runs vsce package --no-dependencies, which requires out/ to already be built (the compile script is chained in automatically).

Running the test suites

./gradlew check                   # gradle-plugin unit + functional tests, CLI tests
cd vscode-extension && npm test   # extension unit tests (mocha)

Troubleshooting

IllegalAccessException: … DirectByteBuffer … modifiers "public"

Robolectric's ShadowVMRuntime.getAddressOfDirectByteBuffer reflects into java.nio.DirectByteBuffer.address(); under JDK 17+ module rules that fails without --add-opens=java.base/java.nio=ALL-UNNAMED. The composePreviewRender Gradle task already adds that opens (along with java.lang and java.lang.reflect) in ComposePreviewPlugin.kt; if a downstream Test task hits this error, mirror the JVM args.

The vscjava.vscode-gradle extension inherits its JDK from redhat.java, which ships a JRE (no jlink). The Android Gradle Plugin's JdkImageTransform needs jlink, so Android builds triggered from inside VS Code fail with:

jlink executable …/redhat.java-*/jre/*/bin/jlink does not exist.

Point both the Java language server and the Gradle integration at a full JDK 17 install in your user settings.json:

{
  "java.jdt.ls.java.home": "/usr/lib/jvm/java-17-openjdk",
  "java.import.gradle.java.home": "/usr/lib/jvm/java-17-openjdk"
}

Adjust the path for your OS (/Library/Java/JavaVirtualMachines/…/Contents/Home on macOS, C:\\Program Files\\Java\\jdk-17 on Windows). Reload the window after changing. Verify with ls $JDK_PATH/bin/jlink — it must exist.

Testing a downstream project against a -SNAPSHOT

Every push to main publishes <next-patch>-SNAPSHOT to Central snapshots. For PR testing, run the Publish snapshot workflow manually from the branch — it produces a branch-qualified version (<next-patch>-<branch-name>-<short-sha>-SNAPSHOT) that won't collide with main. See RELEASING.md § Snapshots for the full recipe and consumer-side pluginManagement block.