Skip to content
Draft
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
50 changes: 50 additions & 0 deletions build-logic/settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
pluginManagement {
repositories {
mavenLocal()
if (settings.extra.has("gradlePluginProxy")) {
maven {
url = uri(settings.extra["gradlePluginProxy"] as String)
isAllowInsecureProtocol = true
}
}
if (settings.extra.has("mavenRepositoryProxy")) {
maven {
url = uri(settings.extra["mavenRepositoryProxy"] as String)
isAllowInsecureProtocol = true
}
}
gradlePluginPortal()
mavenCentral()
}
}

dependencyResolutionManagement {
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
repositories {
mavenLocal()
if (settings.extra.has("mavenRepositoryProxy")) {
maven {
url = uri(settings.extra["mavenRepositoryProxy"] as String)
isAllowInsecureProtocol = true
}
}
gradlePluginPortal()
mavenCentral()
// Hosts gradle-tooling-api; used by the smoke-test plugin to run nested Gradle builds
// pinned to older Gradle versions.
maven {
url = uri("https://repo.gradle.org/gradle/libs-releases")
content {
includeGroup("org.gradle")
}
}
}
}

rootProject.name = "build-logic"

include(":smoke-test")
56 changes: 56 additions & 0 deletions build-logic/smoke-test/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
plugins {
`java-gradle-plugin`
`kotlin-dsl`
`jvm-test-suite`
}

java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

kotlin {
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8)
}
}

dependencies {
implementation(libs.gradle.tooling.api)
runtimeOnly("org.slf4j:slf4j-simple:1.7.36")
}

gradlePlugin {
plugins {
create("smoke-test-app") {
id = "dd-trace-java.smoke-test-app"
implementationClass = "datadog.buildlogic.smoketest.SmokeTestAppPlugin"
}
}
}

@Suppress("UnstableApiUsage")
testing {
suites {
val test by getting(JvmTestSuite::class) {
useJUnitJupiter(libs.versions.junit5)
dependencies {
implementation(libs.junit.jupiter)
implementation(libs.junit.jupiter.params)
implementation(libs.junit.jupiter.engine)
implementation(libs.assertj.core)
implementation(gradleTestKit())
}
targets.configureEach {
testTask.configure {
// The gradle-test-kit runner shells out to a Gradle daemon, which can be slow on a
// cold cache. Surface stdout/stderr to make CI failures debuggable.
testLogging {
showStandardStreams = true
events("failed", "skipped")
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package datadog.buildlogic.smoketest

import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity

/**
* A jar produced by the root build that needs to be forwarded into a [NestedGradleBuild].
*
* At execution time the task adds `-P${propertyName}=<absolute path of file>` to the nested
* Gradle invocation, so the inner build script can pick it up via `findProperty(...)`.
*/
abstract class NestedBuildProjectJar {

@get:Input
abstract val propertyName: Property<String>

@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val file: RegularFileProperty
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package datadog.buildlogic.smoketest

import org.gradle.api.Action
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileTree
import org.gradle.api.file.RegularFile
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.IgnoreEmptyDirectories
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
import org.gradle.jvm.toolchain.JavaLanguageVersion
import org.gradle.jvm.toolchain.JavaLauncher
import org.gradle.jvm.toolchain.JavaToolchainService
import org.gradle.kotlin.dsl.newInstance
import org.gradle.tooling.GradleConnector
import javax.inject.Inject

/**
* Runs a nested Gradle build inside [applicationDir] via the Gradle Tooling API.
*
* Lets a smoke test pin a Gradle version (typically older than the root build) and a Java
* toolchain for the nested daemon, without committing per-application `gradlew` wrappers.
*
* The nested build script is expected to honour `-PappBuildDir=<path>` and redirect its
* `buildDir` to that path so the artifact lands in [applicationBuildDir]. Project artifacts
* from the root build can be forwarded via [projectJar]; each entry is passed as
* `-P<propertyName>=<absolute-path>` and tracked as a task input so the nested build re-runs
* when the upstream jar changes.
*/
abstract class NestedGradleBuild @Inject constructor(
private val objects: ObjectFactory,
javaToolchains: JavaToolchainService,
) : DefaultTask() {

init {
gradleVersion.convention(DEFAULT_NESTED_GRADLE_VERSION)
javaLauncher.convention(
javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(DEFAULT_NESTED_JAVA_VERSION))
},
)
}

@get:Internal
abstract val applicationDir: DirectoryProperty

@get:InputFiles
@get:IgnoreEmptyDirectories
@get:PathSensitive(PathSensitivity.RELATIVE)
val applicationSources: FileTree =
objects.fileTree().from(applicationDir).matching {
exclude(".gradle/**", "build/**")
}

@get:Input
abstract val gradleVersion: Property<String>

@get:Nested
abstract val javaLauncher: Property<JavaLauncher>

@get:Input
abstract val tasksToRun: ListProperty<String>

@get:Input
abstract val buildArguments: ListProperty<String>

@get:Nested
abstract val projectJars: ListProperty<NestedBuildProjectJar>

@get:OutputDirectory
abstract val applicationBuildDir: DirectoryProperty

/** Forward a root-build jar as `-P<name>=<absolute path>` into the nested build. */
fun projectJar(name: String, file: Provider<RegularFile>) {
projectJars.add(
objects.newInstance<NestedBuildProjectJar>().apply {
propertyName.set(name)
this.file.set(file)
},
)
}

/** Configure additional aspects of the nested build via a typed action. */
fun projectJar(action: Action<NestedBuildProjectJar>) {
projectJars.add(
objects.newInstance<NestedBuildProjectJar>().also(action::execute),
)
}

@TaskAction
fun runNestedBuild() {
val appDir = applicationDir.get().asFile
val appBuildDirFile = applicationBuildDir.get().asFile
val daemonJavaHome = javaLauncher.get().metadata.installationPath.asFile

val args = buildList {
add("-PappBuildDir=${appBuildDirFile.absolutePath}")
projectJars.get().forEach { entry ->
add("-P${entry.propertyName.get()}=${entry.file.get().asFile.absolutePath}")
}
addAll(buildArguments.get())
}

val connector = GradleConnector.newConnector()
.useGradleVersion(gradleVersion.get())
.forProjectDirectory(appDir)

connector.connect().use { connection ->
connection.newBuild()
.forTasks(*tasksToRun.get().toTypedArray())
.withArguments(args)
.setJavaHome(daemonJavaHome)
.setStandardOutput(System.out)
.setStandardError(System.err)
.run()
}
}
}
Loading