Valkyrie converts SVG/XML icons to Compose ImageVector Kotlin code. It ships as four tools sharing the same core
pipeline: IDEA/Android Studio plugin, CLI, Gradle plugin, and a WASM web app (WIP).
SVG / XML file
│
▼
components:parser (jvm/svg, jvm/xml, kmp/xml, kmp/svg)
│ produces ──▶ sdk:ir:core (IrImageVector, IrVectorNode, IrPathNode, …)
▼
components:parser:unified (SvgXmlParser — single entry point for both formats)
│
▼
components:generator (kmp/imagevector, jvm/imagevector, iconpack)
│ produces ──▶ Kotlin source string
▼
tools:idea-plugin / tools:cli / tools:gradle-plugin
sdk/ir/core— Intermediate Representation (IR). All domain types live here (IrImageVector,IrVectorNode,IrFill,IrStroke, etc.). Parsers produce IR; generators consume IR.components/parser/unified—SvgXmlParseris the unified entry point used by all tools.components/generator/kmp/imagevector— KMP-compatible generator (used by web/CLI/Gradle); JVM variant (jvm/imagevector) uses KotlinPoet.sdk/shared—ValkyrieModeenum (Simple|IconPack) controls generation style across all tools.build-logic/— Convention plugins (valkyrie.jvm,valkyrie.kmp,valkyrie.abi,valkyrie.compose,valkyrie.kover,valkyrie.wasm.resources) applied viaalias(libs.plugins.valkyrie.*).
| Prefix | Purpose |
|---|---|
sdk/* |
Reusable building blocks (IR, Compose UI, IntelliJ PSI, utils) |
components/* |
Core pipeline: parsers & generators |
tools/* |
End-user deliverables: idea-plugin, cli, gradle-plugin, compose-app |
Public ABI snapshots (<module>/api/*.api) are tracked in 31 modules across all three prefixes, not just
components/* — any module applying valkyrie.abi fails check when its ABI changes.
# Verify before committing (same set CI runs via `./gradlew build`)
./gradlew test # JVM tests of every module
./gradlew spotlessCheck
./gradlew checkKotlinAbi # ABI compatibility check; already part of `check`/`build`
# Fix formatting
./gradlew spotlessApply
# Update ABI snapshots after intentional API changes
./gradlew updateKotlinAbi
# KMP tests beyond JVM (wasmJs needs a local Chrome; usually not what you want to run)
./gradlew allTests
# IDEA plugin
./gradlew buildPlugin # produces tools/idea-plugin/build/distributions/
./gradlew runIde # launches sandbox IDE
# Web / WASM
./gradlew tools:compose-app:wasmJsBrowserDevelopmentRun
# Coverage
./gradlew :sdk:test:coverage:koverLog
./gradlew :sdk:test:coverage:koverHtmlReportJava 25+ is required (settings.gradle.kts enforces this at configuration time). Use a full JDK, not JetBrains
JBR: JBR ships without jlink, so tools:gradle-plugin Android TestKit tests fail inside AGP's JdkImageTransform
when the Gradle daemon runs on JBR (happens when tests are launched from an IDE).
- ktlint via Spotless on all
src/**/*.kt. Run./gradlew spotlessApplybefore pushing. - Compose rules enforced: Material2 disallowed (
compose_disallow_material2 = true), preview naming required (suffix strategy), lambda-param-event-trailing disabled. - KotlinGradle files also linted.
Opt-in building blocks applied via alias(libs.plugins.valkyrie.*), not mutually exclusive:
valkyrie.jvm— JVM-only library (Kotlin JVM,stdlibascompileOnly)valkyrie.kmp— Kotlin Multiplatform (JVM + wasmJs targets,extraWarnings,-Xexpect-actual-classes)valkyrie.compose— JetBrains Compose + Kotlin Compose compiler plugins, registers rootstability_config.confvalkyrie.wasm.resources— serves Compose resources to the wasmJs dev servervalkyrie.abi— binary-compatibility-validator: addscheckKotlinAbitocheckvalkyrie.kover— coverage
tools/* are end-user deliverables, so they declare Kotlin/AGP/application plugins directly and only borrow
valkyrie.abi, valkyrie.kover or valkyrie.compose.
sdk/ir/core/src/commonMain/.../IrImageVector.kt— central domain modelcomponents/parser/unified/src/commonMain/.../SvgXmlParser.kt— unified parser entry pointcomponents/generator/kmp/imagevector/src/commonMain/.../ImageVectorGenerator.kt— KMP generatorsdk/shared/src/commonMain/.../ValkyrieMode.kt— Simple vs IconPack modegradle/libs.versions.toml— main version catalog;gradle/cli.versions.toml,gradle/gradle.versions.toml,gradle/plugin.versions.tomlfor tool-specific versionstools/idea-plugin/CHANGELOG.md— updated via./gradlew tools:idea-plugin:patchChangelog
- Targets IntelliJ IDEA 2026.2.3 (
sinceBuild = "262",untilBuildis unbounded). - Bundled Kotlin/Coroutines/Compose are excluded from the plugin ZIP (classpath clash workaround).
- Platform tests disable the bundled Ultimate module (see the
doFirstblock in the rootbuild.gradle.kts): IU 2026.2.3 registers a core-classloader name that collides with a core interface. - Signing credentials read from env vars:
CERTIFICATE_CHAIN,PRIVATE_KEY,PRIVATE_KEY_PASSWORD. - Publish token:
PUBLISH_TOKEN.
The IDE plugin runs inside the JetBrains runtime, so library versions are ceilings set by the target platform, not
free choices (see the comments in gradle/libs.versions.toml):
intellijIdea("2026.2.3")— floor comes from Jewel, which the Compose UI of the plugin is built on.compose = "1.12.0"— must match the Compose Multiplatform that Jewel of the target IDE was built against; read the Jewel release notes before bumping either side of this pair.kotlin = "2.4.0",coroutines = "1.10.2"— stdlib/coroutines bundled in IJPL 2026.2.xmlutil = "1.0.1"— 1.0.2+ require kotlin-stdlib 2.4.10.ktor = "3.4.3"— 3.5.x+ require kotlinx-coroutines 1.11.0.