diff --git a/all/build.gradle.kts b/all/build.gradle.kts index 2cd16981fbb..a119716fd79 100644 --- a/all/build.gradle.kts +++ b/all/build.gradle.kts @@ -1,3 +1,5 @@ +import java.util.stream.Collectors + plugins { id("otel.java-conventions") } @@ -5,30 +7,13 @@ plugins { description = "OpenTelemetry All" otelJava.moduleName.set("io.opentelemetry.all") -tasks { - // We don't compile much here, just some API boundary tests. This project is mostly for - // aggregating jacoco reports and it doesn't work if this isn't at least as high as the - // highest supported Java version in any of our projects. All of our - // projects target Java 8 except :exporters:http-sender:jdk, which targets - // Java 11 - withType(JavaCompile::class) { - options.release.set(11) - } - - val testJavaVersion: String? by project - if (testJavaVersion == "8") { - test { - enabled = false - } - } -} - // Skip OWASP dependencyCheck task on test module dependencyCheck { skip = true } val testTasks = mutableListOf() +val jarTasks = mutableListOf() dependencies { rootProject.subprojects.forEach { subproject -> @@ -41,6 +26,11 @@ dependencies { subproject.tasks.withType().configureEach { testTasks.add(this) } + subproject.tasks.withType { + if (this.archiveClassifier.get().isEmpty()) { + jarTasks.add(this) + } + } } } } @@ -48,6 +38,44 @@ dependencies { testImplementation("com.tngtech.archunit:archunit-junit5") } +val artifactsAndJarsFile = layout.buildDirectory.file("artifacts_and_jars.txt").get().asFile + +var writeArtifactsAndJars = tasks.register("writeArtifactsAndJars") { + dependsOn(jarTasks) + artifactsAndJarsFile.createNewFile() + val content = jarTasks.stream() + .filter { + !it.archiveFile.get().toString().contains("jmh") + } + .map { + it.archiveBaseName.get() + ":" + it.archiveFile.get().toString() + }.collect(Collectors.joining("\n")) + artifactsAndJarsFile.writeText(content) +} + +tasks { + // We don't compile much here, just some API boundary tests. This project is mostly for + // aggregating jacoco reports and it doesn't work if this isn't at least as high as the + // highest supported Java version in any of our projects. All of our + // projects target Java 8 except :exporters:http-sender:jdk, which targets + // Java 11 + withType(JavaCompile::class) { + options.release.set(11) + } + + val testJavaVersion: String? by project + if (testJavaVersion == "8") { + test { + enabled = false + } + } else { + test { + dependsOn(writeArtifactsAndJars) + environment("ARTIFACTS_AND_JARS", artifactsAndJarsFile.absolutePath) + } + } +} + // https://docs.gradle.org/current/samples/sample_jvm_multi_project_with_code_coverage.html val sourcesPath by configurations.creating { diff --git a/all/src/test/java/io/opentelemetry/all/NoSharedInternalCodeTest.java b/all/src/test/java/io/opentelemetry/all/NoSharedInternalCodeTest.java new file mode 100644 index 00000000000..ae2fb4caf30 --- /dev/null +++ b/all/src/test/java/io/opentelemetry/all/NoSharedInternalCodeTest.java @@ -0,0 +1,79 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.all; + +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; + +import com.tngtech.archunit.base.DescribedPredicate; +import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaClasses; +import com.tngtech.archunit.core.importer.ClassFileImporter; +import com.tngtech.archunit.lang.syntax.elements.ClassesShouldConjunction; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Set; +import java.util.jar.JarFile; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class NoSharedInternalCodeTest { + + private static final String OTEL_BASE_PACKAGE = "io.opentelemetry"; + private static final Logger logger = Logger.getLogger(NoSharedInternalCodeTest.class.getName()); + + @ParameterizedTest + @MethodSource("artifactsAndJars") + void noSharedInternalCode(String artifactId, String absolutePath) throws IOException { + JavaClasses artifactClasses = + new ClassFileImporter().importJar(new JarFile(new File(absolutePath))); + + Set artifactOtelPackages = + artifactClasses.stream() + .map(JavaClass::getPackageName) + .filter(packageName -> packageName.startsWith(OTEL_BASE_PACKAGE)) + .collect(Collectors.toSet()); + + ClassesShouldConjunction noSharedInternalCodeRule = + noClasses() + .that() + .resideInAnyPackage(artifactOtelPackages.toArray(new String[0])) + .should() + .dependOnClassesThat( + new DescribedPredicate<>("are in internal modules of other opentelemetry artifacts") { + @Override + public boolean test(JavaClass javaClass) { + String packageName = javaClass.getPackageName(); + return packageName.startsWith(OTEL_BASE_PACKAGE) + && packageName.contains(".internal") + && !artifactOtelPackages.contains(packageName); + } + }); + + // TODO: when all shared internal code is removed, remove the catch block and fail when detected + try { + noSharedInternalCodeRule.check(artifactClasses); + } catch (AssertionError e) { + logger.log(Level.WARNING, "Internal shared code detected for: " + artifactId + "\n" + e.getMessage() + "\n"); + } + } + + private static Stream artifactsAndJars() throws IOException { + List lines = Files.readAllLines(Path.of(System.getenv("ARTIFACTS_AND_JARS"))); + return lines.stream() + .map(line -> { + String[] parts = line.split(":"); + return Arguments.of(parts[0], parts[1]); + }); + } +}