Skip to content

Commit

Permalink
Support bundling CLI
Browse files Browse the repository at this point in the history
  • Loading branch information
Goooler authored Jul 20, 2024
1 parent a9a77ff commit 6a61cbd
Show file tree
Hide file tree
Showing 4 changed files with 222 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,9 @@ jobs:
java-version: 21
- uses: gradle/actions/setup-gradle@v3
- run: ./gradlew build
- uses: actions/upload-artifact@v4
if: matrix.os == 'ubuntu-latest'
with:
name: svg2compose-binary
path: build/libs/svg2compose-*-binary.jar
if-no-files-found: error
111 changes: 111 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
plugins {
kotlin("jvm") version "2.0.0"
id("com.github.gmazzo.buildconfig") version "5.4.0"
id("maven-publish")
}

group = "br.com.devsrsouza"
version = "0.7.0"
description = "Converts SVG or Android Vector Drawable to Compose code."
val baseName = "svg2compose"

val r8: Configuration by configurations.creating

dependencies {
implementation("com.google.guava:guava:33.2.1-jre")
implementation("com.android.tools:sdk-common:31.5.1")
implementation("com.android.tools:common:31.5.1")
implementation("com.squareup:kotlinpoet:1.18.1")
implementation("org.ogce:xpp3:1.1.6")
implementation("com.github.ajalt.clikt:clikt:4.4.0")

testImplementation(kotlin("test-junit"))

r8("com.android.tools:r8:8.2.47")
}

tasks.test {
Expand All @@ -24,10 +32,113 @@ java {
toolchain.languageVersion = JavaLanguageVersion.of(11)
}

buildConfig {
buildConfigField("VERSION_NAME", version.toString())
buildConfigField("DESCRIPTION", description)
packageName = "br.com.devsrsouza.svg2compose"
}

publishing {
publications {
create<MavenPublication>("maven") {
from(components["kotlin"])
}
}
}

tasks.withType<Jar>().configureEach {
archiveBaseName = baseName
archiveVersion = version.toString()

manifest {
attributes["Main-Class"] = "br.com.devsrsouza.svg2compose.MainKt"
attributes["Implementation-Version"] = version.toString()
}
}

val fatJar by tasks.registering(Jar::class) {
dependsOn(configurations.runtimeClasspath)
dependsOn(tasks.jar)

from(sourceSets.main.map { it.output.classesDirs + it.output.resourcesDir })
from(configurations.runtimeClasspath.map { it.asFileTree.files.map(::zipTree) })

archiveClassifier = "fat"

duplicatesStrategy = DuplicatesStrategy.EXCLUDE

exclude(
"**/*.kotlin_metadata",
"**/*.kotlin_builtins",
"**/*.kotlin_module",
"**/module-info.class",
"assets/**",
"font_metrics.properties",
"META-INF/AL2.0",
"META-INF/DEPENDENCIES",
"META-INF/jdom-info.xml",
"META-INF/LGPL2.1",
"META-INF/maven/**",
"META-INF/native-image/**",
"META-INF/proguard/**",
"META-INF/*.version",
"META-INF/*.SF",
"META-INF/*.DSA",
"META-INF/*.RSA",
"**/*.proto",
"**/*.dex",
"**/LICENSE**",
"**/NOTICE**",
"r8-version.properties",
"migrateToAndroidx/*",
"xsd/catalog.xml",
)
}

val r8File = layout.buildDirectory.file("libs/$baseName-$version-r8.jar").map { it.asFile }
val rulesFile = project.file("src/main/proguard-rules.pro")
val r8Jar by tasks.registering(JavaExec::class) {
dependsOn(fatJar)

val fatJarFile = fatJar.get().archiveFile
inputs.file(fatJarFile)
inputs.file(rulesFile)
outputs.file(r8File)

classpath(r8)
mainClass = "com.android.tools.r8.R8"
args(
"--release",
"--classfile",
"--output", r8File.get().path,
"--pg-conf", rulesFile.path,
"--lib", System.getProperty("java.home"),
fatJarFile.get().toString(),
)
}

val binaryFile = layout.buildDirectory.file("libs/$baseName-$version-binary.jar").map { it.asFile }
val binaryJar by tasks.registering(Task::class) {
dependsOn(r8Jar)

val r8FileProvider = layout.file(r8File)
val binaryFileProvider = layout.file(binaryFile)
inputs.files(r8FileProvider)
outputs.file(binaryFileProvider)

doLast {
val r8File = r8FileProvider.get().asFile
val binaryFile = binaryFileProvider.get().asFile

binaryFile.parentFile.mkdirs()
binaryFile.delete()
binaryFile.writeText("#!/bin/sh\n\nexec java \$JAVA_OPTS -jar \$0 \"\$@\"\n\n")
binaryFile.appendBytes(r8File.readBytes())

binaryFile.setExecutable(true, false)
}
}

tasks.assemble {
dependsOn(binaryJar)
}
90 changes: 90 additions & 0 deletions src/main/kotlin/br/com/devsrsouza/svg2compose/Main.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package br.com.devsrsouza.svg2compose

import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.ParameterHolder
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.options.*
import java.nio.file.Paths
import java.util.*

fun main(vararg args: String) = Svg2ComposeCommand().main(args.toList())

private class Svg2ComposeCommand : CliktCommand(
help = BuildConfig.DESCRIPTION,
) {
private val inputVectorsPath by argument(
"input-vectors-path",
help = "The directory containing the SVG files to be converted."
)
private val outputSourcePath by argument(
"output-source-path",
help = "The output directory of the generated source code."
)

private val applicationIconPackage by stringOption(
"--application-icon-package",
help = "Represents what will be the final package of the generated Vector Source.",
default = "com.example"
)
private val accessorName by stringOption(
"--accessor-name",
help = "The name will be usage to access the Vector in the code like `MyIconPack.IconName` or `MyIconPack.IconGroupDir.IconName`",
default = "Icons"
)
private val allAssetsPropertyName by stringOption(
"--all-assets-property-name",
help = "The name of the property that will contain all assets.",
default = "AllIcons"
)
private val generatePreview by generatePreview()
private val vectorType by vectorType()

init {
versionOption(BuildConfig.VERSION_NAME, names = setOf("-v", "--version"))
}

override fun run() {
Svg2Compose.parse(
applicationIconPackage = applicationIconPackage,
accessorName = accessorName,
outputSourceDirectory = Paths.get(outputSourcePath).toFile(),
vectorsDirectory = Paths.get(inputVectorsPath).toFile(),
type = vectorType,
allAssetsPropertyName = allAssetsPropertyName,
generatePreview = generatePreview
)
}

private fun ParameterHolder.stringOption(
vararg names: String,
help: String,
default: String
): OptionWithValues<String, String, String> {
return option(names = names, help = help).default(default)
}

private fun ParameterHolder.generatePreview(): OptionWithValues<Boolean, Boolean, Boolean> {
return option(
"--generate-preview",
help = "Whether to generate @Preview of the icons. Use 'true' or 'false'."
).convert { arg ->
when (arg.lowercase(Locale.US)) {
"true" -> true
"false" -> false
else -> throw IllegalArgumentException("Invalid value for --generate-preview. Use 'true' or 'false'.")
}
}.default(true)
}

private fun ParameterHolder.vectorType(): OptionWithValues<VectorType, VectorType, VectorType> {
return option("--vector-type", help = "The type of the vector to be generated. Use 'svg' or 'drawable'.")
.convert { arg ->
when (arg.lowercase(Locale.US)) {
"svg" -> VectorType.SVG
"drawable" -> VectorType.DRAWABLE
else -> throw IllegalArgumentException("Invalid value for --vector-type. Use 'svg' or 'drawable'.")
}
}
.default(VectorType.SVG)
}
}
15 changes: 15 additions & 0 deletions src/main/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-allowaccessmodification
-dontobfuscate
-keepattributes SourceFile, LineNumberTable

-keep class br.com.devsrsouza.svg2compose.MainKt {
public static void main(java.lang.String[]);
}

-dontwarn com.google.j2objc.annotations.**
-dontwarn org.graalvm.nativeimage.**
-dontwarn org.graalvm.word.**
-dontwarn com.oracle.svm.core.annotate.**

# TODO: it should be possible to keep less classes under this package.
-keep class org.xmlpull.**

0 comments on commit 6a61cbd

Please sign in to comment.