Skip to content
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
3 changes: 2 additions & 1 deletion LICENSE-3rdparty.csv
Original file line number Diff line number Diff line change
Expand Up @@ -209,10 +209,11 @@ build,commons-codec,Apache-2.0,Copyright 2002-2024 The Apache Software Foundatio
build,commons-io,Apache-2.0,Copyright 2002-2024 The Apache Software Foundation
build,commons-logging,Apache-2.0,Copyright 2002-2024 The Apache Software Foundation
build,io.github.aakira,Apache-2.0,"Copyright (C) 2019 A.Akira"
build,io.gitlab.arturbosch.detekt,Apache-2.0,Copyright 2016-2019 the original author or authors
build,io.github.classgraph,MIT,"Copyright (c) 2019 Luke Hutchison"
build,io.github.java-diff-utils,Apache-2.0,"Copyright 2009-2017 java-diff-utils."
build,io.github.microutils,Apache-2.0,Copyright (c) 2016-2018 Ohad Shai
build,io.github.oshai,Apache-2.0,"Copyright oshai authors"
build,io.gitlab.arturbosch.detekt,Apache-2.0,Copyright 2016-2019 the original author or authors
build,io.grpc,Apache-2.0,Copyright 2014 The gRPC Authors
build,io.netty,Apache-2.0,Copyright 2014 The Netty Project
build,io.opencensus,Apache-2.0,"Copyright 2017, OpenCensus Authors"
Expand Down
9 changes: 6 additions & 3 deletions build-logic/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ dependencies {
compileOnly(libs.detektGradlePlugin)
compileOnly(libs.ktlintGradlePlugin)

// Bundled with Gradle buildscript path
// Kotlin PSI, used by the API surface generator to parse sources. Kept off the plugin runtime
// classpath on purpose: KGP warns (and misbehaves) when a second `kotlin-compiler-embeddable`
// sits next to it on the buildscript classpath. `GenerateApiSurfaceTask` loads it at execution
// time from an isolated worker classpath instead.
compileOnly(libs.kotlinCompilerEmbeddable)

// JsonSchema 2 Poko
Expand All @@ -56,8 +59,8 @@ dependencies {
implementation(libs.kotlinXmlBuilder)

// Tests
// Not inherited from `compileOnly`, and the test JVM has no buildscript classpath to
// borrow it from, so the API surface tests need their own copy.
// Not inherited from `compileOnly`, and the test JVM has no worker classpath to borrow it
// from, so the API surface tests need their own copy.
testImplementation(libs.kotlinCompilerEmbeddable)
testImplementation(libs.bundles.jUnit5)
testImplementation(libs.mockitoKotlin)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,22 @@ internal fun Project.androidLibraryConfig() {

defaultConfig {
minSdk = AndroidConfig.MIN_SDK
aarMetadata {
// TODO RUM-18201
// AGP 9 now sets it to the compileSdk value, imposing consumers
// to use this compile SDK or above. This will force consumers of the library to
// migrate to the latest API level once we bump it on our side.
// Set it compatible with AGP 8.x behavior for now.
minCompileSdk = 1
}
}

compileOptions {
java11()
}

sourceSets.all {
java.srcDir("src/$name/kotlin")
java.directories += "src/$name/kotlin"
}

testOptions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,20 @@ package com.datadog.gradle.config

import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile

internal fun Project.kotlinConfig(
evaluateWarningsAsErrors: Boolean = true,
jvmBytecodeTarget: JvmTarget = JvmTarget.JVM_17
) {
taskConfig<KotlinCompilationTask<KotlinJvmCompilerOptions>> {
taskConfig<KotlinJvmCompile> {
compilerOptions {
jvmTarget.set(jvmBytecodeTarget)
val isCI = System.getenv("CI").toBoolean()
allWarningsAsErrors.set(evaluateWarningsAsErrors && isCI)
apiVersion.set(KotlinVersion.KOTLIN_1_8)
languageVersion.set(KotlinVersion.KOTLIN_1_8)
apiVersion.set(KotlinVersion.KOTLIN_2_0)
languageVersion.set(KotlinVersion.KOTLIN_2_0)
Comment thread
0xnm marked this conversation as resolved.
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import com.datadog.gradle.config.taskConfig
import com.datadog.gradle.plugin.jsonschema.GenerateJsonSchemaTask
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.kotlin.dsl.getByType
import org.gradle.kotlin.dsl.register
import org.gradle.kotlin.dsl.withType
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions
Expand All @@ -29,10 +32,12 @@ class ApiSurfacePlugin : Plugin<Project> {
val compilerMetaFile = apiDir.file("compiler-meta.txt")

val jsonToModelGenerations = target.tasks.withType<GenerateJsonSchemaTask>()
val parserClasspath = target.createParserConfiguration()
val generateApiSurfaceTask = target.tasks
.register<GenerateApiSurfaceTask>(TASK_GEN_KOTLIN_API_SURFACE) {
this.srcDir.set(srcDir)
this.genDir.from(jsonToModelGenerations.map { it.destinationGenDirectory })
this.parserClasspath.from(parserClasspath)
this.surfaceFile.set(kotlinSurfaceFile)
}
target.tasks
Expand Down Expand Up @@ -87,7 +92,30 @@ class ApiSurfacePlugin : Plugin<Project> {
}
}

/**
* Classpath the API surface generator parses sources with. It is resolved as a project
* dependency rather than shipped with this plugin, to keep a second copy of
* `kotlin-compiler-embeddable` off the buildscript classpath, where it would clash with the
* one bundled with the Kotlin Gradle plugin.
*/
private fun Project.createParserConfiguration(): Configuration {
val kotlinVersion = extensions.getByType<VersionCatalogsExtension>()
.named("libs")
.findVersion("kotlin")
.orElseThrow { IllegalStateException("No `kotlin` version found in the version catalog") }
return configurations.create(CONFIGURATION_PARSER) {
isCanBeConsumed = false
isCanBeResolved = true
isVisible = false
dependencies.add(
this@createParserConfiguration.dependencies
.create("org.jetbrains.kotlin:kotlin-compiler-embeddable:$kotlinVersion")
)
}
}

companion object {
const val CONFIGURATION_PARSER = "apiSurfaceParser"
const val TASK_GEN_KOTLIN_API_SURFACE = "generateApiSurface"
const val TASK_GEN_COMPILER_METADATA = "generateCompilerMetadata"
const val TASK_GEN_JAVA_API_SURFACE = "apiDump"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
import java.io.File
import org.gradle.workers.WorkerExecutor
import javax.inject.Inject

@CacheableTask
abstract class GenerateApiSurfaceTask : DefaultTask() {
Expand All @@ -30,10 +32,19 @@ abstract class GenerateApiSurfaceTask : DefaultTask() {
@get:InputFiles
abstract val genDir: ConfigurableFileCollection

/**
* Kotlin compiler jars holding the PSI parser. They are deliberately absent from the plugin
* runtime classpath (see `build-logic/build.gradle.kts`) and only loaded here, inside the
* worker's isolated classloader.
*/
@get:Classpath
abstract val parserClasspath: ConfigurableFileCollection

@get:OutputFile
abstract val surfaceFile: RegularFileProperty

private lateinit var visitor: KotlinFileVisitor
@get:Inject
abstract val workerExecutor: WorkerExecutor

init {
group = "datadog"
Expand All @@ -44,43 +55,15 @@ abstract class GenerateApiSurfaceTask : DefaultTask() {

@TaskAction
fun applyTask() {
visitor = KotlinFileVisitor()
visitDirectoryRecursively(srcDir.get().asFile)
genDir.forEach {
visitDirectoryRecursively(it)
val queue = workerExecutor.classLoaderIsolation {
classpath.from(parserClasspath)
}

surfaceFile.get().asFile.printWriter().use {
it.print(visitor.description.toString())
queue.submit(GenerateApiSurfaceWorkAction::class.java) {
srcDir.set(this@GenerateApiSurfaceTask.srcDir)
genDir.setFrom(this@GenerateApiSurfaceTask.genDir)
surfaceFile.set(this@GenerateApiSurfaceTask.surfaceFile)
}
}

// endregion

private fun visitDirectoryRecursively(file: File) {
when {
!file.exists() -> logger.info("File $file doesn't exist, ignoring")
file.isDirectory ->
file.listFiles().orEmpty()
.sortedBy { it.absolutePath }
.forEach { visitDirectoryRecursively(it) }

file.isFile -> visitFile(file)
else -> logger.error("${file.path} is neither file nor directory")
}
}

private fun visitFile(file: File) {
if (file.canRead()) {
if (file.extension == EXT_KT) {
visitor.visitFile(file)
}
} else {
logger.error("${file.path} is not readable")
}
}

companion object {
const val EXT_KT = "kt"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/

package com.datadog.gradle.plugin.apisurface

import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
import org.slf4j.LoggerFactory
import java.io.File

/**
* Walks the source directories and writes the API surface description.
*
* Runs in a worker with an isolated classloader, which is where the Kotlin PSI classes used by
* [KotlinFileVisitor] come from.
*/
abstract class GenerateApiSurfaceWorkAction : WorkAction<GenerateApiSurfaceWorkAction.Params> {

interface Params : WorkParameters {
val srcDir: DirectoryProperty
val genDir: ConfigurableFileCollection
val surfaceFile: RegularFileProperty
}

private val visitor = KotlinFileVisitor()

override fun execute() {
visitDirectoryRecursively(parameters.srcDir.get().asFile)
parameters.genDir.forEach {
visitDirectoryRecursively(it)
}

parameters.surfaceFile.get().asFile.printWriter().use {
it.print(visitor.description.toString())
}
}

private fun visitDirectoryRecursively(file: File) {
when {
!file.exists() -> LOGGER.info("File {} doesn't exist, ignoring", file)
file.isDirectory ->
file.listFiles().orEmpty()
.sortedBy { it.absolutePath }
.forEach { visitDirectoryRecursively(it) }

file.isFile -> visitFile(file)
else -> LOGGER.error("{} is neither file nor directory", file.path)
}
}

private fun visitFile(file: File) {
if (file.canRead()) {
if (file.extension == EXT_KT) {
visitor.visitFile(file)
}
} else {
LOGGER.error("{} is not readable", file.path)
}
}

companion object {
const val EXT_KT = "kt"
private val LOGGER = LoggerFactory.getLogger(GenerateApiSurfaceWorkAction::class.java)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ abstract class DatadogBuildExtension @Inject constructor(
) {

/** Aligns Kotlin compilation: bytecode target, API/language version and warning strictness. */
@JvmOverloads
fun applyKotlinConfig(
evaluateWarningsAsErrors: Boolean = true,
jvmBytecodeTarget: JvmTarget = JvmTarget.JVM_17
Expand All @@ -51,7 +50,6 @@ abstract class DatadogBuildExtension @Inject constructor(
}

/** Declares the Maven publication (POM metadata, sources/javadoc jars) and its signing. */
@JvmOverloads
fun applyPublishingConfig(
projectDescription: String,
customArtifactId: String = project.name
Expand Down
2 changes: 1 addition & 1 deletion dd-sdk-android-core/api/compiler-meta.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
kotlin_abi_version=1.8.0
kotlin_abi_version=2.0.0
jvm_bytecode_version=11
Loading
Loading