Skip to content

Commit aaaaea5

Browse files
committed
[Gradle plugin] Unify resource directory, rename :generateImageVectors to :generateValkyrieImageVector, create basic README for plugin
1 parent a04a118 commit aaaaea5

File tree

8 files changed

+235
-54
lines changed

8 files changed

+235
-54
lines changed

README.md

Lines changed: 115 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
[![CLI release][badge:cli-release]][url:gh-releases]
1515
[![Homebrew][badge:homebrew]][url:homebrew]
1616

17+
<!-- [![Gradle Plugin Portal][badge:gradle-plugin]][url:gradle-plugin] -->
18+
1719
[![Telegram][badge:telegram-invite]][url:telegram-invite]
1820
[![Slack][badge:slack-invite]][url:slack-invite]
1921
![Test coverage][badge:coverage]
@@ -63,6 +65,9 @@ needs.
6365
- [`svgxml2imagevector` command](#svgxml2imagevector-command)
6466
- [`changelog` command](#changelog-command)
6567
- [Build](#build-cli)
68+
- 🐘 [Gradle plugin](#gradle-plugin)
69+
- [Common scenarios](#common-scenarios)
70+
- [Plugin configuration](#plugin-configuration)
6671
- [Other](#other)
6772
- [Export formats](#export-formats)
6873
- [Comparison with other solutions](#comparison-with-other-solutions)
@@ -86,12 +91,12 @@ needs.
8691

8792
### Available tools:
8893

89-
- [IntelliJ IDEA / Android Studio plugin](#idea-plugin)
90-
- [CLI tool](#cli-tool)
91-
- Gradle plugin (🚧 waiting to publish 🚧)
94+
- 🔌 [IntelliJ IDEA / Android Studio plugin](#idea-plugin)
95+
- 🖥️ [CLI tool](#cli-tool)
96+
- 🐘 [Gradle plugin](#gradle-plugin) (🚧 waiting to publish 🚧)
9297
- Web app (🚧 under development 🚧)
9398

94-
## IDEA Plugin
99+
## 🔌IDEA Plugin
95100

96101
### Plugin features
97102

@@ -236,7 +241,7 @@ folder
236241

237242
or run plugin in IDE using: `./gradlew runIde`
238243

239-
## CLI tool
244+
## 🖥CLI tool
240245

241246
CLI tools can be easily integrated into scripts and automated workflows, allowing you to convert icons from specific
242247
source with predefined settings.
@@ -297,7 +302,7 @@ A part of the CLI tool that allows you to create an icon pack with nested packs.
297302

298303
Usage:
299304

300-
```bash
305+
```text
301306
./valkyrie iconpack [<options>]
302307
```
303308

@@ -315,7 +320,7 @@ A part of the CLI tool that allows you to convert SVG/XML files to ImageVector.
315320

316321
Usage:
317322

318-
```bash
323+
```text
319324
./valkyrie svgxml2imagevector [<options>]
320325
```
321326

@@ -352,6 +357,100 @@ Output example:
352357
Run `./gradlew buildCLI` to build minified version of CLI tool. Artifact will be available in
353358
`tools/cli/build/distributions/valkyrie-cli-*.**.*-SNAPSHOT.zip`.
354359

360+
## 🐘Gradle plugin
361+
362+
The Gradle plugin automates the conversion of SVG/XML files to Compose ImageVector format during the build process. It's
363+
ideal for projects that need to version control icon sources and generate type-safe Kotlin code automatically.
364+
365+
### Common scenarios
366+
367+
- **Team collaboration**: Keep SVG/XML sources in version control and let the build system generate Kotlin code for
368+
everyone
369+
- **CI/CD pipelines**: Ensure icons are always generated consistently across different environments
370+
- **Design system integration**: Automatically sync icon updates from design tools without manual conversion
371+
- **Large icon libraries**: Efficiently manage hundreds or thousands of icons with minimal manual intervention
372+
373+
### Plugin configuration
374+
375+
#### 1. Apply the plugin
376+
377+
<!-- [![Gradle Plugin Portal][badge:gradle-plugin]][url:gradle-plugin] -->
378+
379+
Define in your libs.versions.toml:
380+
381+
```toml
382+
[plugins]
383+
valkyrie = "io.github.composegears.valkyrie:latest-version"
384+
```
385+
386+
Add the plugin to your `build.gradle.kts`:
387+
388+
```kotlin
389+
plugins {
390+
alias(libs.plugins.valkyrie)
391+
}
392+
```
393+
394+
#### 2. Configure the plugin
395+
396+
```kotlin
397+
valkyrie {
398+
// Required: Package name for generated code
399+
// Defaults to Android 'namespace' if Android Gradle Plugin is applied
400+
packageName = "com.example.app.icons"
401+
402+
// Optional: Icon pack configuration
403+
iconPackName = "AppIcons" // Creates an icon pack object (unset by default)
404+
nestedPackName = "Filled" // For nested packs like AppIcons.Filled (unset by default)
405+
406+
// Optional: Resource directory name containing icon files (default: "valkyrieResources")
407+
// Icons will be discovered in src/{sourceSet}/{resourceDirectoryName}/
408+
// Example: src/commonMain/valkyrieResources/, src/androidMain/valkyrieResources/
409+
resourceDirectoryName = "valkyrieResources"
410+
411+
// Optional: Output format for generated ImageVectors (default: BackingProperty)
412+
outputFormat = OutputFormat.BackingProperty // or OutputFormat.LazyProperty
413+
414+
// Optional: Code generation settings
415+
useComposeColors = true // Use androidx.compose.ui.graphics.Color (default: true)
416+
generatePreview = false // Generate @Preview composable functions (default: false)
417+
previewAnnotationType = PreviewAnnotationType.AndroidX // AndroidX or Jetbrains (default: AndroidX)
418+
useFlatPackage = false // Generate flat package structure without subfolders (default: false)
419+
useExplicitMode = false // Add explicit visibility modifiers (default: false)
420+
addTrailingComma = false // Add trailing commas in generated code (default: false)
421+
indentSize = 4 // Number of spaces for indentation (default: 4)
422+
423+
// Optional: Custom output directory (default: build/generated/sources/valkyrie)
424+
outputDirectory = layout.buildDirectory.dir("generated/valkyrie")
425+
426+
// Optional: Generate during IDE sync for better developer experience (default: false)
427+
generateAtSync = false
428+
}
429+
```
430+
431+
#### 3. Organize your icons
432+
433+
Place your icon files in the resources directory:
434+
435+
```text
436+
src/
437+
└── commonMain/
438+
└── valkyrieResources/
439+
├── add.svg
440+
├── delete.svg
441+
└── ic_home.xml
442+
```
443+
444+
The plugin automatically discovers icons from `src/{sourceSet}/valkyrieResources/` in all source sets.
445+
446+
#### 4. Run generation
447+
448+
Run the Gradle task to generate ImageVector sources:
449+
450+
```bash
451+
./gradlew generateValkyrieImageVector
452+
```
453+
355454
## Other
356455

357456
### Export formats
@@ -618,21 +717,21 @@ CLI options `--iconpack-name` and `--nested-packs` removed in favour of `--iconp
618717

619718
Single pack
620719

621-
```
720+
```text
622721
❌ ./valkyrie --iconpack-name=ValkyrieIcons
623722
```
624723

625-
```
724+
```text
626725
✅ ./valkyrie --iconpack=ValkyrieIcons
627726
```
628727

629728
Nested packs
630729

631-
```
730+
```text
632731
❌ ./valkyrie --iconpack-name=ValkyrieIcons --nested-packs=Colored,Filled
633732
```
634733

635-
```
734+
```text
636735
✅ ./valkyrie --iconpack=ValkyrieIcons.Colored,ValkyrieIcons.Filled
637736
```
638737

@@ -652,7 +751,7 @@ Thank you for your contributions and support! ❤️
652751

653752
## License
654753

655-
```
754+
```text
656755
Developed by ComposeGears 2024
657756
658757
Licensed under the Apache License, Version 2.0 (the "License");
@@ -676,6 +775,8 @@ limitations under the License.
676775

677776
[badge:homebrew]: https://img.shields.io/badge/homebrew-tap-orange?style=for-the-badge&labelColor=black&color=white&logo=homebrew
678777

778+
<!-- [badge:gradle-plugin]: https://img.shields.io/gradle-plugin-portal/v/io.github.composegears.valkyrie?style=for-the-badge&labelColor=black&color=white&label=Gradle%20Plugin -->
779+
679780
[badge:marketplace-downloads]: https://img.shields.io/jetbrains/plugin/d/24786.svg?style=for-the-badge&labelColor=black&color=white
680781

681782
[badge:marketplace-rating]: https://img.shields.io/jetbrains/plugin/r/rating/24786?style=for-the-badge&labelColor=black&color=white
@@ -694,6 +795,8 @@ limitations under the License.
694795

695796
[url:homebrew]: https://github.com/ComposeGears/homebrew-repo
696797

798+
<!-- [url:gradle-plugin]: https://plugins.gradle.org/plugin/io.github.composegears.valkyrie -->
799+
697800
[url:telegram-invite]: https://t.me/composegears
698801

699802
[url:slack-invite]: https://join.slack.com/t/composegears/shared_invite/zt-2noleve52-D~zrFPmC1cdhThsuQUW61A

tools/gradle-plugin/api/gradle-plugin.api

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,15 @@ public abstract class io/github/composegears/valkyrie/gradle/GenerateImageVector
22
public fun <init> ()V
33
public final fun execute ()V
44
public abstract fun getAddTrailingComma ()Lorg/gradle/api/provider/Property;
5-
public abstract fun getDrawableFiles ()Lorg/gradle/api/file/ConfigurableFileCollection;
65
public abstract fun getGeneratePreview ()Lorg/gradle/api/provider/Property;
6+
public abstract fun getIconFiles ()Lorg/gradle/api/file/ConfigurableFileCollection;
77
public abstract fun getIconPackName ()Lorg/gradle/api/provider/Property;
88
public abstract fun getIndentSize ()Lorg/gradle/api/provider/Property;
99
public abstract fun getNestedPackName ()Lorg/gradle/api/provider/Property;
1010
public abstract fun getOutputDirectory ()Lorg/gradle/api/file/DirectoryProperty;
1111
public abstract fun getOutputFormat ()Lorg/gradle/api/provider/Property;
1212
public abstract fun getPackageName ()Lorg/gradle/api/provider/Property;
1313
public abstract fun getPreviewAnnotationType ()Lorg/gradle/api/provider/Property;
14-
public abstract fun getSvgFiles ()Lorg/gradle/api/file/ConfigurableFileCollection;
1514
public abstract fun getUseComposeColors ()Lorg/gradle/api/provider/Property;
1615
public abstract fun getUseExplicitMode ()Lorg/gradle/api/provider/Property;
1716
public abstract fun getUseFlatPackage ()Lorg/gradle/api/provider/Property;
@@ -28,6 +27,7 @@ public abstract interface class io/github/composegears/valkyrie/gradle/ValkyrieE
2827
public abstract fun getOutputFormat ()Lorg/gradle/api/provider/Property;
2928
public abstract fun getPackageName ()Lorg/gradle/api/provider/Property;
3029
public abstract fun getPreviewAnnotationType ()Lorg/gradle/api/provider/Property;
30+
public abstract fun getResourceDirectoryName ()Lorg/gradle/api/provider/Property;
3131
public abstract fun getUseComposeColors ()Lorg/gradle/api/provider/Property;
3232
public abstract fun getUseExplicitMode ()Lorg/gradle/api/provider/Property;
3333
public abstract fun getUseFlatPackage ()Lorg/gradle/api/provider/Property;

tools/gradle-plugin/src/main/kotlin/io/github/composegears/valkyrie/gradle/GenerateImageVectorsTask.kt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,7 @@ import org.gradle.api.tasks.TaskAction
2626

2727
@CacheableTask
2828
abstract class GenerateImageVectorsTask : DefaultTask() {
29-
@get:[PathSensitive(RELATIVE) InputFiles] abstract val svgFiles: ConfigurableFileCollection
30-
31-
@get:[PathSensitive(RELATIVE) InputFiles] abstract val drawableFiles: ConfigurableFileCollection
29+
@get:[PathSensitive(RELATIVE) InputFiles] abstract val iconFiles: ConfigurableFileCollection
3230

3331
@get:Input abstract val packageName: Property<String>
3432

@@ -92,7 +90,7 @@ abstract class GenerateImageVectorsTask : DefaultTask() {
9290
useFlatPackage = useFlatPackage,
9391
)
9492

95-
(svgFiles + drawableFiles).files.forEach { file ->
93+
iconFiles.files.forEach { file ->
9694
val parseOutput = SvgXmlParser.toIrImageVector(ParserType.Jvm, Path(file.absolutePath))
9795
val vectorSpecOutput = ImageVectorGenerator.convert(
9896
vector = parseOutput.irImageVector,
@@ -130,6 +128,7 @@ abstract class GenerateImageVectorsTask : DefaultTask() {
130128
}
131129

132130
internal companion object {
133-
internal const val TASK_NAME = "generateImageVectors"
131+
internal const val TASK_NAME = "generateValkyrieImageVector"
132+
internal const val DEFAULT_RESOURCE_DIRECTORY = "valkyrieResources"
134133
}
135134
}

tools/gradle-plugin/src/main/kotlin/io/github/composegears/valkyrie/gradle/Utils.kt

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import org.gradle.api.Project
88
import org.gradle.api.file.ConfigurableFileCollection
99
import org.gradle.api.file.Directory
1010
import org.gradle.api.file.FileCollection
11+
import org.gradle.api.provider.Property
1112
import org.gradle.api.provider.Provider
1213
import org.gradle.util.GradleVersion
1314
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
@@ -21,13 +22,10 @@ internal fun registerTask(
2122
target.tasks.register(taskName, GenerateImageVectorsTask::class.java) { task ->
2223
task.description = "Converts SVG & Drawable files into ImageVector Kotlin accessor properties"
2324

24-
val svgFiles = sourceSet.findSvgFiles()
25-
val drawableFiles = sourceSet.findDrawableFiles()
26-
task.svgFiles.conventionCompat(svgFiles)
27-
task.drawableFiles.conventionCompat(drawableFiles)
28-
task.onlyIf("Needs at least one input file") {
29-
!svgFiles.isEmpty || !drawableFiles.isEmpty
30-
}
25+
val resourceDirName = extension.resourceDirectoryName
26+
val iconFiles = sourceSet.findIconFiles(resourceDirName)
27+
task.iconFiles.conventionCompat(iconFiles)
28+
task.onlyIf("Needs at least one input file") { !iconFiles.isEmpty }
3129

3230
task.packageName.convention(extension.packageName.orElse(target.packageNameOrThrow()))
3331

@@ -96,12 +94,11 @@ private fun KotlinSourceSet.root(): Directory = with(project) {
9694
return layout.dir(src).get()
9795
}
9896

99-
private fun KotlinSourceSet.findSvgFiles(): FileCollection = root()
100-
.dir("svg")
101-
.asFileTree
102-
.filter { it.extension == "svg" }
103-
104-
private fun KotlinSourceSet.findDrawableFiles(): FileCollection = root()
105-
.dir("res")
106-
.asFileTree
107-
.filter { it.parentFile?.name.orEmpty().contains("drawable") && it.extension == "xml" }
97+
private fun KotlinSourceSet.findIconFiles(resourceDirectoryName: Property<String>): FileCollection {
98+
return root()
99+
.dir(resourceDirectoryName.get())
100+
.asFileTree
101+
.filter {
102+
it.extension == "svg" || it.extension == "xml"
103+
}
104+
}

tools/gradle-plugin/src/main/kotlin/io/github/composegears/valkyrie/gradle/ValkyrieExtension.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ interface ValkyrieExtension {
2222
*/
2323
val outputDirectory: DirectoryProperty
2424

25+
/**
26+
* Name of the resource directory containing icon files. Defaults to `valkyrieResources`.
27+
* The plugin will look for icons in `<sourceSet>/valkyrieResources` for all project types.
28+
*/
29+
val resourceDirectoryName: Property<String>
30+
2531
/**
2632
* Unset by default
2733
*/

tools/gradle-plugin/src/main/kotlin/io/github/composegears/valkyrie/gradle/ValkyrieGradlePlugin.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package io.github.composegears.valkyrie.gradle
22

33
import io.github.composegears.valkyrie.generator.jvm.imagevector.OutputFormat
44
import io.github.composegears.valkyrie.generator.jvm.imagevector.PreviewAnnotationType
5+
import io.github.composegears.valkyrie.gradle.GenerateImageVectorsTask.Companion.DEFAULT_RESOURCE_DIRECTORY
56
import io.github.composegears.valkyrie.gradle.GenerateImageVectorsTask.Companion.TASK_NAME
67
import org.gradle.api.Plugin
78
import org.gradle.api.Project
@@ -19,6 +20,7 @@ class ValkyrieGradlePlugin : Plugin<Project> {
1920
packageName.convention(packageNameOrThrow())
2021
generateAtSync.convention(false)
2122
outputDirectory.convention(layout.buildDirectory.dir("generated/sources/valkyrie"))
23+
resourceDirectoryName.convention(DEFAULT_RESOURCE_DIRECTORY)
2224
outputFormat.convention(OutputFormat.BackingProperty)
2325
useComposeColors.convention(true)
2426
generatePreview.convention(false)

tools/gradle-plugin/src/test/kotlin/io/github/composegears/valkyrie/gradle/Utils.kt

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import assertk.Assert
66
import assertk.assertions.isEqualTo
77
import assertk.assertions.isNotNull
88
import assertk.fail
9+
import io.github.composegears.valkyrie.gradle.GenerateImageVectorsTask.Companion.DEFAULT_RESOURCE_DIRECTORY
910
import java.nio.file.Files
1011
import java.nio.file.Path
1112
import java.nio.file.Paths
@@ -55,16 +56,22 @@ internal fun Path.writeSettingsFile() = resolve("settings.gradle.kts").writeText
5556
""".trimIndent(),
5657
)
5758

58-
internal fun Path.writeTestSvgs(sourceSet: String) {
59-
val destDir = resolve("src/$sourceSet/svg")
59+
internal fun Path.writeTestSvgs(
60+
sourceSet: String,
61+
resourceDirName: String = DEFAULT_RESOURCE_DIRECTORY,
62+
) {
63+
val destDir = resolve("src/$sourceSet/$resourceDirName")
6064
destDir.createDirectories()
6165

6266
val sourceDir = RESOURCES_DIR_SVG.toPath()
6367
sourceDir.copyToRecursively(destDir, followLinks = true)
6468
}
6569

66-
internal fun Path.writeTestDrawables(sourceSet: String) {
67-
val destDir = resolve("src/$sourceSet/res/drawable")
70+
internal fun Path.writeTestDrawables(
71+
sourceSet: String,
72+
resourceDirName: String = DEFAULT_RESOURCE_DIRECTORY,
73+
) {
74+
val destDir = resolve("src/$sourceSet/$resourceDirName")
6875
destDir.createDirectories()
6976

7077
val sourceDir = RESOURCES_DIR_XML.toPath()
@@ -73,7 +80,10 @@ internal fun Path.writeTestDrawables(sourceSet: String) {
7380

7481
internal fun Assert<BuildResult>.taskWasSuccessful(name: String) = taskHadResult(name, SUCCESS)
7582

76-
internal fun Assert<BuildResult>.taskHadResult(path: String, expected: TaskOutcome) = transform { it.task(path)?.outcome }
83+
internal fun Assert<BuildResult>.taskHadResult(
84+
path: String,
85+
expected: TaskOutcome,
86+
) = transform { it.task(path)?.outcome }
7787
.isNotNull()
7888
.isEqualTo(expected)
7989

0 commit comments

Comments
 (0)