Skip to content

Commit

Permalink
Enable using Kotlin in Ion Java (#488)
Browse files Browse the repository at this point in the history
  • Loading branch information
popematt authored and tgregg committed Jan 18, 2024
1 parent 55e7240 commit 88a06a5
Show file tree
Hide file tree
Showing 29 changed files with 317 additions and 274 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jobs:
- java: 11
upload_reports: true
- java: 17
fail-fast: false
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
Expand All @@ -30,6 +31,10 @@ jobs:
- uses: gradle/gradle-build-action@842c587ad8aa4c68eeba24c396e15af4c2e9f30a # v2.9.0
with:
arguments: build
- name: Test minified JAR
uses: gradle/gradle-build-action@842c587ad8aa4c68eeba24c396e15af4c2e9f30a # v2.9.0
with:
arguments: minifyTest
- run: ./ion-test-driver-run version
- if: ${{ matrix.upload_reports }}
uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # v3.1.4
Expand Down
17 changes: 17 additions & 0 deletions THIRD_PARTY_LICENSES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

# ion-java
## Dependency License Report

## Apache License, Version 2.0

**1** **Group:** `org.jetbrains` **Name:** `annotations` **Version:** `13.0`
> - **POM Project URL**: [http://www.jetbrains.org](http://www.jetbrains.org)
> - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)
**2** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-stdlib` **Version:** `1.9.0`
> - **POM Project URL**: [https://kotlinlang.org/](https://kotlinlang.org/)
> - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)
**3** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-stdlib-common` **Version:** `1.9.0`
> - **POM Project URL**: [https://kotlinlang.org/](https://kotlinlang.org/)
> - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)
188 changes: 182 additions & 6 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,17 +1,38 @@
import com.github.jk1.license.filter.LicenseBundleNormalizer
import com.github.jk1.license.render.InventoryMarkdownReportRenderer
import com.github.jk1.license.render.TextReportRenderer
import org.jetbrains.kotlin.gradle.dsl.KotlinCompile
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions
import proguard.gradle.ProGuardTask
import java.net.URI
import java.time.Instant
import java.util.Properties

buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("com.guardsquare:proguard-gradle:7.4.0")
}
}

plugins {
kotlin("jvm") version "1.9.0"
java
`maven-publish`
jacoco
signing
id("com.github.johnrengelman.shadow") version "8.1.1"

id("org.cyclonedx.bom") version "1.7.2"
id("com.github.spotbugs") version "5.0.13"
id("org.jlleitschuh.gradle.ktlint") version "11.3.2"
// TODO: more static analysis. E.g.:
// id("com.diffplug.spotless") version "6.11.0"

// Used for generating the third party attribution document
id("com.github.jk1.dependency-license-report") version "2.5"
}

jacoco {
Expand All @@ -20,9 +41,12 @@ jacoco {

repositories {
mavenCentral()
google()
}

dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.9.0")

testImplementation("org.junit.jupiter:junit-jupiter:5.7.1")
testCompileOnly("junit:junit:4.13")
testRuntimeOnly("org.junit.vintage:junit-vintage-engine")
Expand All @@ -38,27 +62,155 @@ version = File(project.rootDir.path + "/project.version").readLines().single()
description = "A Java implementation of the Amazon Ion data notation."

val isReleaseVersion: Boolean = !version.toString().endsWith("SNAPSHOT")
val generatedJarInfoDir = "${buildDir}/generated/jar-info"
val generatedResourcesDir = "$buildDir/generated/main/resources"
lateinit var sourcesArtifact: PublishArtifact
lateinit var javadocArtifact: PublishArtifact

sourceSets {
main {
java.srcDir("src")
resources.srcDir(generatedJarInfoDir)
resources.srcDir(generatedResourcesDir)
}
test {
java.srcDir("test")
}
}

licenseReport {
// Because of the current gradle project structure, we must explicitly exclude ion-java-cli, even
// though ion-java does not depend on ion-java-cli. By default, the license report generator includes
// the current project (ion-java) and all its subprojects.
projects = arrayOf(project)
outputDir = "$buildDir/reports/licenses"
renderers = arrayOf(InventoryMarkdownReportRenderer(), TextReportRenderer())
// Dependencies use inconsistent titles for Apache-2.0, so we need to specify mappings
filters = arrayOf(
LicenseBundleNormalizer(
mapOf(
"The Apache License, Version 2.0" to "Apache-2.0",
"The Apache Software License, Version 2.0" to "Apache-2.0",
)
)
)
}

tasks {
withType<JavaCompile> {
options.encoding = "UTF-8"
// Because we set the `release` option, you can no longer build ion-java using JDK 8. However, we continue to
// emit JDK 8 compatible classes due to widespread use of this library with JDK 8.
options.release.set(8)
}
withType<KotlinCompile<KotlinJvmOptions>> {
kotlinOptions {
// Kotlin jvmTarget must match the JavaCompile release version
jvmTarget = "1.8"
}
}

jar {
archiveClassifier.set("original")
}

// Creates a super jar of ion-java and its dependencies where all dependencies are shaded (moved)
// to com.amazon.ion_.shaded.are_you_sure_you_want_to_use_this
shadowJar {
val newLocation = "com.amazon.ion.shaded_.do_not_use"
archiveClassifier.set("shaded")
dependsOn(generateLicenseReport)
from(generateLicenseReport.get().outputFolder)
relocate("kotlin", "$newLocation.kotlin")
relocate("org.jetbrains", "$newLocation.org.jetbrains")
relocate("org.intellij", "$newLocation.org.intellij")
dependencies {
// Remove all Kotlin metadata so that it looks like an ordinary Java Jar
exclude("**/*.kotlin_metadata")
exclude("**/*.kotlin_module")
exclude("**/*.kotlin_builtins")
// Eliminate dependencies' pom files
exclude("**/pom.*")
}
}

/**
* The `minifyJar` task uses Proguard to create a JAR that is smaller than the combined size of ion-java
* and its dependencies. This is the final JAR that is published to maven central.
*/
val minifyJar by register<ProGuardTask>("minifyJar") {
group = "build"
val rulesPath = file("config/proguard/rules.pro")
val inputJarPath = shadowJar.get().outputs.files.singleFile
val outputJarPath = "build/libs/ion-java-$version.jar"

inputs.file(rulesPath)
inputs.file(inputJarPath)
outputs.file(outputJarPath)
dependsOn(shadowJar)
dependsOn(configurations.runtimeClasspath)

injars(inputJarPath)
outjars(outputJarPath)
configuration(rulesPath)

val javaHome = System.getProperty("java.home")
// Automatically handle the Java version of this build, but we don't support lower than JDK 11
// See https://github.com/Guardsquare/proguard/blob/e76e47953f6f295350a3bb7eeb801b33aac34eae/examples/gradle-kotlin-dsl/build.gradle.kts#L48-L60
libraryjars(
mapOf("jarfilter" to "!**.jar", "filter" to "!module-info.class"),
"$javaHome/jmods/java.base.jmod"
)
}

build {
dependsOn(minifyJar)
}

generateLicenseReport {
doLast {
// We don't want the time in the generated markdown report because that makes it unstable for
// our verification of the THIRD_PARTY_LICENSES file.
val markdownReport = outputs.files.single().walk().single { it.path.endsWith(".md") }
val dateRegex = Regex("^_\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2} \\w+_$")
// Reads the content of the markdown report, replacing the date line with an empty line, and
// trimming extra whitespace from the end of all other lines.
val newMarkdownContent = markdownReport.readLines()
.joinToString("\n") { if (it.matches(dateRegex)) "" else it.trimEnd() }
markdownReport.writeText(newMarkdownContent)
}
}

// Task to check whether the THIRD_PARTY_LICENSES file is still up-to-date.
val checkThirdPartyLicensesFile by register("checkThirdPartyLicensesFile") {
val thirdPartyLicensesFileName = "THIRD_PARTY_LICENSES.md"
val thirdPartyLicensesPath = "$rootDir/$thirdPartyLicensesFileName"
dependsOn(generateLicenseReport)
inputs.file(thirdPartyLicensesPath)
group = "verification"
description = "Verifies that $thirdPartyLicensesFileName is up-to-date."
doLast {
val generatedMarkdownReport = generateLicenseReport.get().outputs.files.single()
.walk().single { it.path.endsWith(".md") }
val generatedMarkdownReportContent = generatedMarkdownReport.readLines()
.filter { it.isNotBlank() }
.joinToString("\n")

val sourceControlledMarkdownReport = File(thirdPartyLicensesPath)
val sourceControlledMarkdownReportContent = sourceControlledMarkdownReport.readLines()
.filter { it.isNotBlank() }
.joinToString("\n")

if (sourceControlledMarkdownReportContent != generatedMarkdownReportContent) {
throw IllegalStateException(
"$thirdPartyLicensesPath is out of date.\n" +
"Please replace the file content with the content of $generatedMarkdownReport."
)
}
}
}

check {
dependsOn(checkThirdPartyLicensesFile)
}

javadoc {
// Suppressing Javadoc warnings is clunky, but there doesn't seem to be any nicer way to do it.
Expand All @@ -72,6 +224,11 @@ tasks {
}
}

ktlint {
version.set("0.40.0")
outputToConsole.set(true)
}

// spotbugs-gradle-plugin creates a :spotbugsTest task by default, but we don't want it
// see: https://github.com/spotbugs/spotbugs-gradle-plugin/issues/391
project.gradle.startParameter.excludedTaskNames.add(":spotbugsTest")
Expand Down Expand Up @@ -110,7 +267,7 @@ tasks {
"xsltproc",
"--output", spotbugsBaselineFile,
"$rootDir/config/spotbugs/baseline.xslt",
outputLocation.get().toString()
"${outputLocation.get()}/spotBugs"
)
}
}
Expand All @@ -136,15 +293,15 @@ tasks {
* for why this is done with a properties file rather than the Jar manifest.
*/
val generateJarInfo by creating<Task> {
val propertiesFile = File("$generatedResourcesDir/${project.name}.properties")
doLast {
val propertiesFile = File("$generatedJarInfoDir/${project.name}.properties")
propertiesFile.parentFile.mkdirs()
val properties = Properties()
properties.setProperty("build.version", version.toString())
properties.setProperty("build.time", Instant.now().toString())
properties.store(propertiesFile.writer(), null)
}
outputs.dir(generatedJarInfoDir)
outputs.file(propertiesFile)
}

processResources { dependsOn(generateJarInfo) }
Expand All @@ -160,14 +317,33 @@ tasks {
}
}

test {
fun Test.applyCommonTestConfig() {
group = "verification"
maxHeapSize = "1g" // When this line was added Xmx 512m was the default, and we saw OOMs
maxParallelForks = Math.max(1, Runtime.getRuntime().availableProcessors() / 2)
useJUnitPlatform()
// report is always generated after tests run
finalizedBy(jacocoTestReport)
}

test {
applyCommonTestConfig()
}

/** Runs the JUnit test on the minified jar. */
register<Test>("minifyTest") {
applyCommonTestConfig()
classpath = project.configurations.testRuntimeClasspath.get() + project.sourceSets.test.get().output + minifyJar.outputs.files
dependsOn(minifyJar)
}

/** Runs the JUnit test on the shadow jar. */
register<Test>("shadowTest") {
applyCommonTestConfig()
classpath = project.configurations.testRuntimeClasspath.get() + project.sourceSets.test.get().output + shadowJar.get().outputs.files
dependsOn(minifyJar)
}

withType<Sign> {
setOnlyIf { isReleaseVersion && gradle.taskGraph.hasTask(":publish") }
}
Expand Down
6 changes: 6 additions & 0 deletions config/proguard/rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# See https://www.guardsquare.com/manual/configuration/usage
-keep class !com.amazon.ion.shaded_.** { *; }
-keepattributes *Annotation*,Signature,InnerClasses,EnclosingMethod
-dontoptimize
-dontobfuscate
-dontwarn java.sql.Timestamp
Loading

0 comments on commit 88a06a5

Please sign in to comment.