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
1 change: 0 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ plugins {

alias(libs.plugins.shadow) apply false
alias(libs.plugins.spotless)
id("com.github.spotbugs") version "6.5.11"
id("io.github.gradle-nexus.publish-plugin") version "2.0.0"
id("me.champeau.gradle.japicmp") version "0.4.3"
id("me.champeau.jmh") version "0.7.3" apply false
Expand Down
8 changes: 8 additions & 0 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import org.gradle.api.attributes.java.TargetJvmVersion

plugins {
`java-gradle-plugin`
`kotlin-dsl`
Expand Down Expand Up @@ -94,6 +96,12 @@ repositories {
dependencies {
implementation(gradleApi())
implementation(libs.forbiddenapis)
implementation(libs.spotbugs.gradle.plugin) {
// Select the plugin's Java 11 variant while buildSrc still has to targets Java 8 for muzzle workers.
attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 11)
}
}

implementation("net.bytebuddy", "byte-buddy-gradle-plugin", libs.versions.byte.buddy.get())

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
plugins {
java
id("de.thetaphi.forbiddenapis")
}

forbiddenApis {
signaturesFiles = files(rootDir.resolve("gradle/forbiddenApiFilters/main.txt"))
ignoreSignaturesOfMissingClasses = true
ignoreFailures = false
}

tasks.matching { it.name.startsWith("forbiddenApis") && it.name.endsWith("Test") }.configureEach {
enabled = false
}

dependencies {
compileOnly(versionCatalogs.named("libs").findLibrary("forbiddenapis").get())
Comment thread
bric3 marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import com.github.spotbugs.snom.SpotBugsTask

plugins {
java
id("com.github.spotbugs")
}

spotbugs {
ignoreFailures = false
// Exclude known types and packages
excludeFilter = rootDir.resolve("gradle/spotbugFilters/exclude.xml")
showProgress = !providers.environmentVariable("CI").isPresent
}

val productionTaskName = Regex("spotbugsMain(?:_java\\d+)?")

tasks.withType<SpotBugsTask>().configureEach {
when {
project.path.startsWith(":dd-smoke-tests") -> {
enabled = false
}
productionTaskName.matches(name) -> {
omitVisitors = listOf(
"ConstructorThrow",
"DefaultEncodingDetector",
"DoInsideDoPrivileged",
"DontUseEnum",
"DroppedException",
"FindDeadLocalStores",
"FindHEmismatch",
"FindNullDeref",
"FindReturnRef",
"FindRunInvocations",
"FindUselessControlFlow",
"InitializationChain",
"LazyInit",
"LoadOfKnownNullValue",
"LostLoggerDueToWeakReference",
"MethodReturnCheck",
"MutableStaticFields",
"Naming",
"RuntimeExceptionCapture",
"SerializableIdiom",
"UnreadFields",
)
}
else -> {
// static-analysis-rules skill: enable selected test tasks here with their own detector configuration.
enabled = false
}
}

launcher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.current()
}

val reportFile = layout.buildDirectory.file("reports/spotbugs/$name.html")
reports {
create("html") {
required = true
outputLocation = reportFile
setStylesheet("fancy-hist.xsl")
}
}
}

val libs = versionCatalogs.named("libs")
dependencies {
compileOnly(libs.findLibrary("spotbugs-annotations").get())
testImplementation(libs.findLibrary("jsr305").get())
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import datadog.gradle.plugin.instrument.BuildTimeInstrumentationExtension

plugins {
id("dd-trace-java.conventions.java")
id("de.thetaphi.forbiddenapis")
id("dd-trace-java.conventions.forbidden-apis")
}

// These plugins are produced by buildSrc and are only available at runtime.
Expand All @@ -26,10 +26,7 @@ configurations.named("muzzleBootstrap") {
tasks.withType<Javadoc>().configureEach { enabled = false }

forbiddenApis {
signaturesFiles = files(
"$rootDir/gradle/forbiddenApiFilters/main.txt",
"$rootDir/gradle/forbiddenApiFilters/instrumentation.txt"
)
signaturesFiles += files(rootDir.resolve("gradle/forbiddenApiFilters/instrumentation.txt"))
}

val libs = versionCatalogs.named("libs")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package datadog.gradle.plugin.quality

import datadog.gradle.plugin.GradleFixture
import org.assertj.core.api.Assertions.assertThat
import org.gradle.testkit.runner.TaskOutcome
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import java.io.File

class ForbiddenApisConventionPluginTest : GradleFixture() {
@BeforeEach
fun setup() {
writeSettings("""rootProject.name = "forbidden-apis-convention-test"""")
writeFile("gradle/libs.versions.toml", File("../gradle/libs.versions.toml").readText())
writeFile(
"gradle/forbiddenApiFilters/main.txt",
"""
java.lang.System#exit(int)
missing.OptionalDependency
"""
)
}

@Test
fun `preserves defaults and name-based exclusions for late source sets and tasks`() {
addSubproject(
"library",
"""
import de.thetaphi.forbiddenapis.gradle.CheckForbiddenApis

plugins {
id("dd-trace-java.conventions.forbidden-apis")
}

sourceSets.create("latestDepTest")
sourceSets.create("main_java11")
sourceSets.create("testFixtures")
sourceSets.create("jmh")
tasks.register("forbiddenApisSyntheticTest")
tasks.register("unrelatedTest")

tasks.register("verifyDefaults") {
doLast {
val mainFilter = rootProject.file("gradle/forbiddenApiFilters/main.txt")
tasks.withType<CheckForbiddenApis>().forEach {
check(it.signaturesFiles.files == setOf(mainFilter))
check(it.ignoreSignaturesOfMissingClasses)
check(!it.ignoreFailures)
}
listOf("forbiddenApisTest", "forbiddenApisLatestDepTest", "forbiddenApisSyntheticTest").forEach {
check(!tasks.getByName(it).enabled)
}
listOf("forbiddenApisMain", "forbiddenApisMain_java11", "forbiddenApisTestFixtures", "forbiddenApisJmh", "unrelatedTest").forEach {
check(tasks.getByName(it).enabled)
}
val aggregate = tasks.getByName("forbiddenApis")
val checkTask = tasks.getByName("check")
check(aggregate in checkTask.taskDependencies.getDependencies(checkTask))
check(tasks.getByName("forbiddenApisMain_java11") in aggregate.taskDependencies.getDependencies(aggregate))
val dependency = configurations.getByName("compileOnly").dependencies.single {
it.group == "de.thetaphi" && it.name == "forbiddenapis"
}
check(dependency.version == libs.versions.forbiddenapis.get())
check(configurations.getByName("runtimeClasspath").allDependencies.none { it.name == "forbiddenapis" })
}
}
"""
)

val result = run(":library:verifyDefaults")

assertThat(result.task(":library:verifyDefaults")?.outcome)
.describedAs(result.output).isEqualTo(TaskOutcome.SUCCESS)
}

@Test
fun `preserves extension and typed task overrides`() {
writeFile("gradle/forbiddenApiFilters/instrumentation.txt", "java.lang.Runtime#exit(int)")
writeRootProject(
"""
import de.thetaphi.forbiddenapis.gradle.CheckForbiddenApis

plugins {
id("dd-trace-java.conventions.forbidden-apis")
}

forbiddenApis {
signaturesFiles += files("gradle/forbiddenApiFilters/instrumentation.txt")
ignoreFailures = true
}
tasks.named<CheckForbiddenApis>("forbiddenApisMain") {
failOnMissingClasses = false
}
tasks.named("forbiddenApisTest") {
enabled = true
}

tasks.register("verifyOverrides") {
doLast {
val main = tasks.named<CheckForbiddenApis>("forbiddenApisMain").get()
check(main.signaturesFiles.files == setOf(file("gradle/forbiddenApiFilters/main.txt"), file("gradle/forbiddenApiFilters/instrumentation.txt")))
check(main.ignoreFailures)
check(!main.failOnMissingClasses)
check(tasks.getByName("forbiddenApisTest").enabled)
}
}
"""
)

val result = run("verifyOverrides")

assertThat(result.task(":verifyOverrides")?.outcome)
.describedAs(result.output).isEqualTo(TaskOutcome.SUCCESS)
}

@Test
fun `fails check on a forbidden call and allows compile-only suppression`() {
writeRootProject(
"""
plugins {
id("dd-trace-java.conventions.forbidden-apis")
}
repositories {
mavenCentral()
}
"""
)
writeJavaSource("Example", "public class Example { public void exit() { System.exit(0); } }")

val failed = run("check", expectFailure = true)

assertThat(failed.task(":forbiddenApisMain")?.outcome)
.describedAs(failed.output).isEqualTo(TaskOutcome.FAILED)
assertThat(failed.output).contains("java.lang.System#exit(int)")

writeJavaSource(
"Example",
"""
import de.thetaphi.forbiddenapis.SuppressForbidden;

public class Example {
@SuppressForbidden
public void exit() { System.exit(0); }
}
"""
)

val suppressed = run("check")

assertThat(suppressed.task(":forbiddenApisMain")?.outcome)
.describedAs(suppressed.output).isEqualTo(TaskOutcome.SUCCESS)
assertThat(suppressed.task(":forbiddenApisTest")?.outcome).isEqualTo(TaskOutcome.SKIPPED)
assertThat(suppressed.task(":check")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
}
}
Loading
Loading