Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rudimentary CLI #58

Merged
merged 4 commits into from
Feb 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions cli/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
plugins {
`kotlin-conventions`
`library-publishing-conventions`
alias(libs.plugins.shadow)
}

tasks {
shadowJar {
manifest {
attributes["Main-Class"] = "com.toasttab.expediter.cli.ExpediterCliCommandKt"
}
}

test {
systemProperty("classes", sourceSets.main.get().kotlin.classesDirectory.get().asFile.path)
systemProperty("libraries", configurations.getAt("testRuntimeClasspath").asPath)
}
}

dependencies {
implementation(libs.clikt)
implementation(libs.protobuf.java)
implementation(projects.core)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright (c) 2024 Toast Inc.
*
* 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 com.toasttab.expediter.cli

import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.options.default
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.toasttab.expediter.Expediter
import com.toasttab.expediter.ignore.Ignore
import com.toasttab.expediter.issue.IssueOrder
import com.toasttab.expediter.issue.IssueReport
import com.toasttab.expediter.provider.ClasspathApplicationTypesProvider
import com.toasttab.expediter.provider.InMemoryPlatformTypeProvider
import com.toasttab.expediter.provider.JvmTypeProvider
import com.toasttab.expediter.provider.PlatformTypeProvider
import com.toasttab.expediter.roots.RootSelector
import com.toasttab.expediter.types.ClassfileSource
import com.toasttab.expediter.types.ClassfileSourceType
import protokt.v1.toasttab.expediter.v1.TypeDescriptors
import java.io.File

class ExpediterCliCommand : CliktCommand() {
val output: String by option().required()
val projectClasses: List<String> by option().multiple()
val libraries: List<String> by option().multiple()
val ignoresFiles: List<String> by option().multiple()
val jvmPlatform: String? by option()
val platformDescriptors: String? by option()
val projectName: String by option().default("project")

fun ignores() = ignoresFiles.flatMap {
File(it).inputStream().use {
IssueReport.fromJson(it).issues
}
}.toSet()

fun appTypes() = ClasspathApplicationTypesProvider(
projectClasses.map { ClassfileSource(File(it), ClassfileSourceType.SOURCE_SET, it) } +
libraries.map { ClassfileSource(File(it), ClassfileSourceType.EXTERNAL_DEPENDENCY, it) }
)

fun platform(): PlatformTypeProvider {
val jvm = jvmPlatform?.let(String::toInt)
val platformFile = platformDescriptors?.let(::File)

return if (jvm != null) {
JvmTypeProvider.forTarget(jvm)
} else if (platformFile != null) {
InMemoryPlatformTypeProvider(
platformFile.inputStream().use {
TypeDescriptors.deserialize(it)
}.types
)
} else {
error("Must specify either jvm version or platform descriptors")
}
}
override fun run() {
val issues = Expediter(
ignore = Ignore.SpecificIssues(ignores()),
appTypes = appTypes(),
platformTypeProvider = platform(),
rootSelector = RootSelector.ProjectClasses
).findIssues()

val issueReport = IssueReport(
projectName,
issues.sortedWith(IssueOrder.TYPE)
)

issueReport.issues.forEach {
System.err.println(it)
}

File(output).outputStream().buffered().use {
issueReport.toJson(it)
}
}
}

fun main(args: Array<String>) = ExpediterCliCommand().main(args)
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright (c) 2024 Toast Inc.
*
* 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 com.toasttab.expediter.cli

import com.toasttab.expediter.issue.Issue
import com.toasttab.expediter.issue.IssueReport
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.io.TempDir
import strikt.api.expectThat
import strikt.assertions.contains
import java.io.File
import java.nio.file.Path
import kotlin.io.path.inputStream

class ExpediterCliCommandIntegrationTest {
@TempDir
lateinit var dir: Path

@Test
fun `run on self`() {
val output = dir.resolve("expediter.json")

ExpediterCliCommand().main(
System.getProperty("libraries").split(File.pathSeparatorChar).flatMap {
listOf("--libraries", it)
} + listOf(
"--project-classes", System.getProperty("classes"),
"--output", output.toString(),
"--jvm-platform", "11"
)
)

val report = output.inputStream().use {
IssueReport.fromJson(it)
}

expectThat(report.issues).contains(
Issue.MissingType(
caller = "com/github/ajalt/mordant/internal/nativeimage/WinKernel32Lib",
target = "org/graalvm/word/PointerBase"
)
)
}
}
6 changes: 6 additions & 0 deletions core/src/main/kotlin/com/toasttab/expediter/ignore/Ignore.kt
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ interface Ignore : Serializable {
val IS_BLANK = Signature("()V")
}
}

class SpecificIssues(
private val issues: Set<Issue>
) : Ignore {
override fun ignore(issue: Issue) = issues.contains(issue)
}
}

@JvmInline
Expand Down
32 changes: 32 additions & 0 deletions core/src/main/kotlin/com/toasttab/expediter/issue/IssueOrder.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2024 Toast Inc.
*
* 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 com.toasttab.expediter.issue

object IssueOrder {
val CALLER: Comparator<Issue> = compareBy({
it.caller
}, {
it.target
})

val TYPE: Comparator<Issue> = compareBy({
it::class.java.name
}, {
it.target
}, {
it.caller
})
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package com.toasttab.expediter.roots

import com.toasttab.expediter.types.ApplicationType
import com.toasttab.expediter.types.ClassfileSourceType

interface RootSelector {
fun isRoot(type: ApplicationType): Boolean

object All : RootSelector {
override fun isRoot(type: ApplicationType) = true
}

object ProjectClasses : RootSelector {
override fun isRoot(type: ApplicationType) = type.source.type == ClassfileSourceType.SOURCE_SET || type.source.type == ClassfileSourceType.SUBPROJECT_DEPENDENCY
}
}
5 changes: 5 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
kotlin = "1.9.22"
serialization = "1.6.3"
asm = "9.6"
clikt = "4.2.2"

nexus = "1.3.0"
ktlint = "0.50.0"
spotless = "6.25.0"
protokt = "1.0.0-beta.1"
protobuf = "3.25.3"
shadow = "8.1.1"

# test
junit = "5.10.2"
Expand All @@ -18,6 +20,8 @@ testkit-plugin = "0.0.4"
# main
serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization"}
asm = { module = "org.ow2.asm:asm", version.ref = "asm" }
clikt = { module = "com.github.ajalt.clikt:clikt", version.ref = "clikt"}

# plugins
nexus-publish = { module = "io.github.gradle-nexus:publish-plugin", version.ref = "nexus" }
kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
Expand All @@ -36,3 +40,4 @@ serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref
gradle-publish = { id = "com.gradle.plugin-publish", version = "1.2.1" }
testkit-plugin = { id = "com.toasttab.testkit" , version.ref = "testkit-plugin" }
protokt = { id = "com.toasttab.protokt", version.ref = "protokt" }
shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" }
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import com.toasttab.expediter.Expediter
import com.toasttab.expediter.gradle.config.RootType
import com.toasttab.expediter.gradle.service.ApplicationTypeCache
import com.toasttab.expediter.ignore.Ignore
import com.toasttab.expediter.issue.IssueOrder
import com.toasttab.expediter.issue.IssueReport
import com.toasttab.expediter.parser.TypeParsers
import com.toasttab.expediter.provider.InMemoryPlatformTypeProvider
Expand Down Expand Up @@ -169,20 +170,15 @@ abstract class ExpediterTask : DefaultTask() {
logger.info("type sources = {}", typeSources)

val issues = Expediter(
ignore,
Ignore.Or(ignore, Ignore.SpecificIssues(ignores)),
cache.get().resolve(typeSources),
PlatformTypeProviderChain(providers),
roots.selector,
).findIssues().subtract(ignores)
).findIssues().sortedWith(IssueOrder.CALLER)

val issueReport = IssueReport(
project.name,
issues.sortedWith(
compareBy(
{ it.caller },
{ it.target }
)
)
issues
)

for (issue in issueReport.issues) {
Expand Down
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ apply(plugin = "net.vivin.gradle-semantic-build-versioning")
include(
":model",
":core",
":cli",
":plugin",
":tests",
":tests:lib1",
Expand Down
Loading