Skip to content

Commit

Permalink
Add compile subcommand to zipline-cli
Browse files Browse the repository at this point in the history
Update Gradle plugin to invoke CLI to perform compilation. This allows compilation to not be subject to the version of Kotlin on the Gradle buildscript classpath.
  • Loading branch information
JakeWharton committed Sep 7, 2024
1 parent d06cbb8 commit 63be40f
Show file tree
Hide file tree
Showing 28 changed files with 199 additions and 64 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

* New: "compile" subcommand in 'zipline-cli' compiles `.js` files to `.zipline` files.


## [1.17.0] - 2024-08-28
[1.17.0]: https://github.com/cashapp/zipline/releases/tag/1.17.0
Expand Down
7 changes: 5 additions & 2 deletions zipline-cli/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,19 @@ artifacts {
kotlin {
sourceSets {
all {
languageSettings.optIn("app.cash.zipline.EngineApi")
languageSettings.optIn("kotlinx.serialization.ExperimentalSerializationApi")
}
}
}

dependencies {
api(projects.ziplineApiValidator)
api(projects.ziplineLoader)
implementation(projects.ziplineApiValidator)
implementation(projects.ziplineBytecode)
implementation(projects.ziplineLoader)
implementation(libs.clikt)
implementation(libs.okHttp.core)
implementation(libs.kotlinx.serialization.json)

testImplementation(projects.ziplineLoaderTesting)
testImplementation(libs.assertk)
Expand Down
104 changes: 104 additions & 0 deletions zipline-cli/src/main/kotlin/app/cash/zipline/cli/Compile.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Copyright (C) 2024 Cash App
*
* 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 app.cash.zipline.cli

import app.cash.zipline.loader.ManifestSigner
import app.cash.zipline.loader.SignatureAlgorithmId
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.options.associate
import com.github.ajalt.clikt.parameters.options.convert
import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.help
import com.github.ajalt.clikt.parameters.options.multiple
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.options.required
import com.github.ajalt.clikt.parameters.types.file
import okio.ByteString.Companion.decodeHex

internal class Compile : CliktCommand(
name = "compile",
help = "Compile .js files to .zipline files",
) {
private val inputDir by option("--input").file().required()
.help("Directory from which .js files will be loaded.")

private val outputDir by option("--output").file().required()
.help("Directory into which .zipline files will be written.")

private val addedFiles by option("--added").file().multiple()
.help("Input files added since the last compile. This will trigger incremental compilation.")
private val removedFiles by option("--removed").file().multiple()
.help("Input files removed since the last compile. This will trigger incremental compilation.")
private val modifiedFiles by option("--modified").file().multiple()
.help("Input files modified since the last compile. This will trigger incremental compilation.")

private val mainFunction by option()
private val mainModuleId by option()
private val version by option()
private val stripLineNumbers by option().flag()

private val signatures by option("--signature")
.help(
"""
|Signature to sign outputs.
|
|Format: "id:name:key" where 'id' is one of ${SignatureAlgorithmId.entries} and 'key' is a hex-encoded private key.
""".trimMargin(),
)
.convert {
val (idName, name, keyHex) = it.split(':', limit = 3)
val id = SignatureAlgorithmId.valueOf(idName)
val key = keyHex.decodeHex()
Triple(id, name, key)
}
.multiple()

private val metadata by option().associate()

override fun run() {
val manifestSigner = if (signatures.isEmpty()) {
null
} else {
ManifestSigner.Builder()
.apply {
for ((id, name, key) in signatures) {
add(id, name, key)
}
}
.build()
}

val compiler = ZiplineCompiler(
outputDir = outputDir,
mainFunction = mainFunction,
mainModuleId = mainModuleId,
manifestSigner = manifestSigner,
version = version,
metadata = metadata,
stripLineNumbers = stripLineNumbers,
)

if (addedFiles.size or removedFiles.size or modifiedFiles.size != 0) {
compiler.incrementalCompile(
modifiedFiles = modifiedFiles,
addedFiles = addedFiles,
removedFiles = removedFiles,
)
} else {
compiler.compile(inputDir)
}
}
}
1 change: 1 addition & 0 deletions zipline-cli/src/main/kotlin/app/cash/zipline/cli/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ fun main(vararg args: String) {

NoOpCliktCommand()
.subcommands(
Compile(),
Download(),
GenerateKeyPair(),
ValidateZiplineApi(NAME_CHECK),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package app.cash.zipline.gradle
package app.cash.zipline.cli

import app.cash.zipline.QuickJs
import app.cash.zipline.ZiplineManifest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

package app.cash.zipline.gradle
package app.cash.zipline.cli

import app.cash.zipline.QuickJs
import app.cash.zipline.ZiplineManifest
Expand Down
2 changes: 0 additions & 2 deletions zipline-gradle-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,12 @@ kotlin {
dependencies {
implementation(kotlin("gradle-plugin-api"))
implementation(projects.zipline)
implementation(projects.ziplineBytecode)
implementation(projects.ziplineKotlinPlugin)
implementation(projects.ziplineLoader)
implementation(libs.http4k.core)
implementation(libs.http4k.server.jetty)
implementation(libs.http4k.client.websocket)
implementation(libs.kotlin.gradle.plugin)
implementation(libs.kotlinx.serialization.json)
implementation(libs.okHttp.core)
implementation(libs.okio.core)
testImplementation(projects.ziplineLoaderTesting)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,38 @@

package app.cash.zipline.gradle

import app.cash.zipline.loader.ManifestSigner
import app.cash.zipline.loader.SignatureAlgorithmId
import java.io.File
import java.io.Serializable
import javax.inject.Inject
import okio.ByteString
import okio.ByteString.Companion.decodeHex
import org.gradle.api.DefaultTask
import org.gradle.api.artifacts.Configuration
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
import org.gradle.work.ChangeType
import org.gradle.process.ExecOperations
import org.gradle.work.ChangeType.ADDED
import org.gradle.work.ChangeType.MODIFIED
import org.gradle.work.ChangeType.REMOVED
import org.gradle.work.Incremental
import org.gradle.work.InputChanges
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsBinaryMode

/**
* Compiles `.js` files to `.zipline` files.
*/
abstract class ZiplineCompileTask : DefaultTask() {
abstract class ZiplineCompileTask @Inject constructor(
private val execOperations: ExecOperations,
) : DefaultTask() {

@get:Incremental
@get:InputDirectory
Expand Down Expand Up @@ -72,13 +79,19 @@ abstract class ZiplineCompileTask : DefaultTask() {
@get:Input
abstract val stripLineNumbers: Property<Boolean>

@get:Classpath
abstract val classpath: ConfigurableFileCollection

internal fun configure(
outputDirectoryName: String,
jsProductionTask: JsProductionTask,
extension: ZiplineExtension,
cliConfiguration: Configuration,
) {
description = "Compile .js to .zipline"

classpath.setFrom(cliConfiguration)

inputDir.fileProvider(jsProductionTask.outputFile.map { it.asFile.parentFile })

outputDir.set(project.layout.buildDirectory.dir("zipline/$outputDirectoryName"))
Expand Down Expand Up @@ -109,51 +122,63 @@ abstract class ZiplineCompileTask : DefaultTask() {

@TaskAction
fun task(inputChanges: InputChanges) {
val inputDirFile = inputDir.get().asFile
val outputDirFile = outputDir.get().asFile
val mainModuleId = mainModuleId.orNull
val mainFunction = mainFunction.orNull
val signingKeys = signingKeys.get()
val manifestSigner = when {
signingKeys.isNotEmpty() -> {
val builder = ManifestSigner.Builder()
for (signingKey in signingKeys) {
builder.add(signingKey.algorithm, signingKey.name, signingKey.privateKey)
val args = buildList {
add("compile")
add("--input")
add(inputDir.get().asFile.toString())
add("--output")
add(outputDir.get().asFile.toString())
mainModuleId.orNull?.let {
add("--main-module-id")
add(it)
}
mainFunction.orNull?.let {
add("--main-function")
add(it)
}
for (signingKey in signingKeys.get()) {
add("--signer")
add(
buildString {
append(signingKey.algorithm.name)
append(':')
append(signingKey.name)
append(':')
append(signingKey.privateKey.hex())
},
)
}
version.orNull?.let {
add("--version")
add(it)
}
metadata.orNull?.let {
for ((key, value) in it) {
add("--metadata")
add("$key=$value")
}
builder.build()
}
else -> null
}
val version = version.orNull
val metadata = metadata.orNull ?: mapOf()
val stripLineNumbers = stripLineNumbers.orNull ?: false

val ziplineCompiler = ZiplineCompiler(
outputDir = outputDirFile,
mainFunction = mainFunction,
mainModuleId = mainModuleId,
manifestSigner = manifestSigner,
version = version,
metadata = metadata,
stripLineNumbers = stripLineNumbers,
)

if (inputChanges.isIncremental) {
fun filterByChangeType(filter: (ChangeType) -> Boolean): List<File> {
return inputChanges.getFileChanges(inputDir)
.filter { filter(it.changeType) }
.map { outputDir.file(it.normalizedPath).get().asFile }
if (stripLineNumbers.getOrElse(false)) {
add("--strip-line-numbers")
}
if (inputChanges.isIncremental) {
for (fileChange in inputChanges.getFileChanges(inputDir)) {
add(
when (fileChange.changeType) {
ADDED -> "--added"
MODIFIED -> "--modified"
REMOVED -> "--removed"
},
)
add(fileChange.file.toString())
}
}
}

ziplineCompiler.incrementalCompile(
modifiedFiles = filterByChangeType { changeType -> changeType == ChangeType.MODIFIED },
addedFiles = filterByChangeType { changeType -> changeType == ChangeType.ADDED },
removedFiles = filterByChangeType { changeType -> changeType == ChangeType.REMOVED },
)
} else {
ziplineCompiler.compile(
inputDir = inputDirFile,
)
execOperations.javaexec { exec ->
exec.classpath = classpath
exec.mainClass.set("app.cash.zipline.cli.Main")
exec.setArgs(args)
}
}

Expand Down
Loading

0 comments on commit 63be40f

Please sign in to comment.