Skip to content

Commit

Permalink
Expose API checks as a CLI (#1061)
Browse files Browse the repository at this point in the history
* Expose API checks as a CLI

Don't depend on the zipline-api-validator code in the Gradle runtime;
instead fork a process and call this CLI.

Also migrate from Picocli to Clikt.

* Track the CLI in sample apps

* Address code review feedback
  • Loading branch information
squarejesse authored Jun 27, 2023
1 parent 7d087ba commit 1e9f043
Show file tree
Hide file tree
Showing 20 changed files with 391 additions and 348 deletions.
3 changes: 3 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ kotlin.mpp.androidSourceSetLayoutVersion1.nowarn=true
kotlin.mpp.stability.nowarn=true
kotlin.mpp.commonizerLogLevel=info
kotlin.mpp.enableCInteropCommonization=true

# Signals to our own plugin that we are building within the repo.
app.cash.zipline.internal=true
4 changes: 1 addition & 3 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ kotlinx-serialization = "1.5.0"
okHttp = "4.11.0"
okio = "3.3.0"
minSdk = "18"
picocli = "4.7.4"
sqldelight = "1.5.5"

[libraries]
Expand Down Expand Up @@ -56,8 +55,7 @@ okHttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okHttp" }
okHttp-mockWebServer = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "okHttp" }
okio-core = { module = "com.squareup.okio:okio", version.ref = "okio" }
okio-fakeFileSystem = { module = "com.squareup.okio:okio-fakefilesystem", version.ref = "okio" }
picocli = { module = "info.picocli:picocli", version.ref = "picocli" }
picocli-compiler = { module = "info.picocli:picocli-codegen", version.ref = "picocli" }
clikt = { module = "com.github.ajalt.clikt:clikt", version = "3.5.4" }
shadowJar-gradle-plugin = { module = "gradle.plugin.com.github.johnrengelman:shadow", version = "8.0.0" }
sqldelight-gradle-plugin = { module = "com.squareup.sqldelight:gradle-plugin", version.ref = "sqldelight" }
sqldelight-driver-android = { module = "com.squareup.sqldelight:android-driver", version.ref = "sqldelight" }
Expand Down
16 changes: 6 additions & 10 deletions samples/settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,11 @@ dependencyResolutionManagement {

includeBuild('..') {
dependencySubstitution {
substitute module("app.cash.zipline:zipline") using project(
":zipline")
substitute module("app.cash.zipline:zipline-gradle-plugin") using project(
":zipline-gradle-plugin")
substitute module("app.cash.zipline:zipline-kotlin-plugin") using project(
":zipline-kotlin-plugin")
substitute module("app.cash.zipline:zipline-loader") using project(
":zipline-loader")
substitute module("app.cash.zipline:zipline-profiler") using project(
":zipline-profiler")
substitute module("app.cash.zipline:zipline") using project(":zipline")
substitute module("app.cash.zipline:zipline-cli") using project(":zipline-cli")
substitute module("app.cash.zipline:zipline-gradle-plugin") using project(":zipline-gradle-plugin")
substitute module("app.cash.zipline:zipline-kotlin-plugin") using project(":zipline-kotlin-plugin")
substitute module("app.cash.zipline:zipline-loader") using project(":zipline-loader")
substitute module("app.cash.zipline:zipline-profiler") using project(":zipline-profiler")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ import org.jetbrains.kotlin.types.Variance

fun readFirZiplineApi(
sources: Collection<File>,
dependencies: Collection<File>,
classpath: Collection<File>,
): FirZiplineApi {
return KotlinFirLoader(sources, dependencies).use { loader ->
return KotlinFirLoader(sources, classpath).use { loader ->
val output = loader.load("zipline-api-dump")
FirZiplineApiReader(output).read()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ import org.jetbrains.kotlin.platform.jvm.JvmPlatforms
*/
internal class KotlinFirLoader(
private val sources: Collection<File>,
private val dependencies: Collection<File>,
private val classpath: Collection<File>,
) : AutoCloseable {
private val disposable = Disposer.newDisposable()

Expand Down Expand Up @@ -79,7 +79,7 @@ internal class KotlinFirLoader(
configuration.addKotlinSourceRoots(sources.map { it.absolutePath })
// TODO Figure out how to add the JDK modules to the classpath. Currently importing the stdlib
// allows a typealias to resolve to a JDK type which doesn't exist and thus breaks analysis.
configuration.addJvmClasspathRoots(dependencies.filter { "kotlin-stdlib-" !in it.path })
configuration.addJvmClasspathRoots(classpath.filter { "kotlin-stdlib-" !in it.path })

val environment = KotlinCoreEnvironment.createForProduction(
disposable,
Expand Down
5 changes: 1 addition & 4 deletions zipline-cli/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import com.vanniktech.maven.publish.KotlinJvm

plugins {
kotlin("jvm")
kotlin("kapt")
application
id("com.github.gmazzo.buildconfig")
id("com.vanniktech.maven.publish.base")
Expand Down Expand Up @@ -47,10 +46,8 @@ kotlin {
dependencies {
api(projects.ziplineApiValidator)
api(projects.ziplineLoader)
implementation(libs.clikt)
implementation(libs.okHttp.core)
implementation(libs.picocli)

kapt(libs.picocli.compiler)

testImplementation(projects.ziplineLoaderTesting)
testImplementation(libs.assertk)
Expand Down
46 changes: 17 additions & 29 deletions zipline-cli/src/main/kotlin/app/cash/zipline/cli/Download.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,46 +16,34 @@

package app.cash.zipline.cli

import app.cash.zipline.cli.Download.Companion.NAME
import app.cash.zipline.loader.ManifestVerifier.Companion.NO_SIGNATURE_CHECKS
import app.cash.zipline.loader.ZiplineLoader
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.options.help
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.options.required
import com.github.ajalt.clikt.parameters.types.path
import java.io.File
import java.util.concurrent.Executors
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.runBlocking
import okhttp3.OkHttpClient
import okio.FileSystem
import okio.Path.Companion.toOkioPath
import picocli.CommandLine.Command
import picocli.CommandLine.Option

@Command(
name = NAME,
description = ["Recursively download Zipline code to a directory from a URL"],
mixinStandardHelpOptions = true,
versionProvider = Main.VersionProvider::class,
)
class Download : Runnable {
@Option(
names = ["-A", "--application-name"],
description = ["Application name for the Zipline Manifest."],
required = true,
)
lateinit var applicationName: String
class Download : CliktCommand(NAME) {
private val applicationName by option("-A", "--application-name")
.required()
.help("Application name for the Zipline Manifest.")

@Option(
names = ["-M", "--manifest-url"],
description = ["URL to the Zipline Manifest for the code to download."],
required = true,
)
lateinit var manifestUrl: String
private val manifestUrl by option("-M", "--manifest-url")
.required()
.help("URL to the Zipline Manifest for the code to download.")

@Option(
names = ["-D", "--download-dir"],
description = ["Directory where code will be downloaded to."],
required = true,
)
lateinit var downloadDir: File
private val downloadDir by option("-D", "--download-dir")
.path(canBeFile = false)
.required()
.help("Directory where code will be downloaded to.")

private val executorService = Executors.newSingleThreadExecutor { Thread(it, "Zipline") }
private val dispatcher = executorService.asCoroutineDispatcher()
Expand All @@ -82,7 +70,7 @@ class Download : Runnable {
download(
applicationName = applicationName,
manifestUrl = manifestUrl,
downloadDir = downloadDir,
downloadDir = downloadDir.toFile(),
)
}

Expand Down
37 changes: 17 additions & 20 deletions zipline-cli/src/main/kotlin/app/cash/zipline/cli/GenerateKeyPair.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,36 +16,33 @@

package app.cash.zipline.cli

import app.cash.zipline.cli.GenerateKeyPair.Companion.NAME
import app.cash.zipline.loader.SignatureAlgorithmId
import app.cash.zipline.loader.SignatureAlgorithmId.Ed25519
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.options.default
import com.github.ajalt.clikt.parameters.options.help
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.types.enum
import java.io.PrintStream
import picocli.CommandLine.Command
import picocli.CommandLine.Option

@Command(
name = NAME,
description = [
"Generate an Ed25519 key pair for ManifestSigner and ManifestVerifier",
],
mixinStandardHelpOptions = true,
versionProvider = Main.VersionProvider::class,
)
class GenerateKeyPair(
private val out: PrintStream = System.out,
) : Runnable {
@Option(
names = ["-a", "--algorithm"],
description = ["Signing algorithm to use."],
)
var algorithm: SignatureAlgorithmId = Ed25519
) : CliktCommand(NAME) {
private val algorithm by option("-a", "--algorithm")
.enum<SignatureAlgorithmId>()
.default(Ed25519)
.help("Signing algorithm to use.")

@Suppress("INVISIBLE_MEMBER") // Access :zipline-loader internals.
override fun run() {
val keyPair = app.cash.zipline.loader.internal.generateKeyPair(algorithm)
out.println(" ALGORITHM: $algorithm")
out.println(" PUBLIC KEY: ${keyPair.publicKey.hex()}")
out.println("PRIVATE KEY: ${keyPair.privateKey.hex()}")
out.println(
"""
| ALGORITHM: $algorithm
| PUBLIC KEY: ${keyPair.publicKey.hex()}
|PRIVATE KEY: ${keyPair.privateKey.hex()}
""".trimMargin(),
)
}

companion object {
Expand Down
70 changes: 18 additions & 52 deletions zipline-cli/src/main/kotlin/app/cash/zipline/cli/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,62 +13,28 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:JvmName("Main")

package app.cash.zipline.cli

import app.cash.zipline.cli.Main.Companion.NAME
import kotlin.system.exitProcess
import picocli.CommandLine
import picocli.CommandLine.Command
import picocli.CommandLine.HelpCommand
import picocli.CommandLine.IVersionProvider
import picocli.CommandLine.Model.CommandSpec
import picocli.CommandLine.Option
import picocli.CommandLine.ParameterException
import picocli.CommandLine.Spec
import app.cash.zipline.cli.ValidateZiplineApi.Companion.NAME_CHECK
import app.cash.zipline.cli.ValidateZiplineApi.Companion.NAME_DUMP
import com.github.ajalt.clikt.core.NoOpCliktCommand
import com.github.ajalt.clikt.core.subcommands
import com.github.ajalt.clikt.parameters.options.versionOption

@Command(
name = NAME,
description = ["Use Zipline without Gradle."],
mixinStandardHelpOptions = true,
synopsisSubcommandLabel = "COMMAND",
subcommands = [Download::class, GenerateKeyPair::class, HelpCommand::class],
versionProvider = Main.VersionProvider::class,
)
class Main : Runnable {
@Spec
lateinit var spec: CommandSpec

@Option(names = ["--completionScript"], hidden = true)
var completionScript: Boolean = false

override fun run() {
if (completionScript) {
println(picocli.AutoComplete.bash("zipline-cli", CommandLine(Main())))
return
}

throw ParameterException(spec.commandLine(), "Missing required subcommand")
}

class VersionProvider : IVersionProvider {
override fun getVersion(): Array<String> {
return arrayOf(
"$NAME ${BuildConfig.VERSION}",
)
}
fun main(vararg args: String) {
if (System.getProperty("javax.net.debug") == null) {
System.setProperty("javax.net.debug", "")
}

companion object {
internal const val NAME = "zipline-cli"

@JvmStatic
fun main(args: Array<String>) {
if (System.getProperty("javax.net.debug") == null) {
System.setProperty("javax.net.debug", "")
}

exitProcess(CommandLine(Main()).execute(*args))
}
}
NoOpCliktCommand()
.subcommands(
Download(),
GenerateKeyPair(),
ValidateZiplineApi(NAME_CHECK),
ValidateZiplineApi(NAME_DUMP),
)
.versionOption(BuildConfig.VERSION)
.main(args)
}
Loading

0 comments on commit 1e9f043

Please sign in to comment.