diff --git a/all/build.gradle.kts b/all/build.gradle.kts index 560e9184ca4..91e14ff21c2 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().forEach { + if (it.archiveClassifier.get().isEmpty() && !it.archiveFile.get().toString().contains("jmh")) { + jarTasks.add(it) + } + } } } } @@ -48,6 +38,38 @@ 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.parentFile.mkdirs() + artifactsAndJarsFile.createNewFile() + val content = jarTasks.stream() + .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 + test { + enabled = testJavaVersion != "8" + 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..ba37e80d985 --- /dev/null +++ b/all/src/test/java/io/opentelemetry/all/NoSharedInternalCodeTest.java @@ -0,0 +1,119 @@ +/* + * 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.Arrays; +import java.util.HashSet; +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 Set exemptions = + new HashSet<>( + Arrays.asList( + "opentelemetry-api-incubator", + "opentelemetry-exporter-common", + "opentelemetry-exporter-logging", + "opentelemetry-exporter-logging-otlp", + "opentelemetry-exporter-prometheus", + "opentelemetry-exporter-zipkin", + "opentelemetry-extension-trace-propagators", + "opentelemetry-opencensus-shim", + "opentelemetry-sdk-common", + "opentelemetry-sdk-logs", + "opentelemetry-sdk-metrics", + "opentelemetry-sdk-testing", + "opentelemetry-sdk-trace", + "opentelemetry-sdk-extension-autoconfigure", + "opentelemetry-sdk-extension-autoconfigure-spi", + "opentelemetry-sdk-extension-incubator", + "opentelemetry-sdk-extension-jaeger-remote-sampler", + "opentelemetry-exporter-otlp", + "opentelemetry-exporter-otlp-common", + "opentelemetry-exporter-sender-grpc-managed-channel", + "opentelemetry-exporter-sender-jdk", + "opentelemetry-exporter-sender-okhttp")); + + 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); + } + }); + + try { + noSharedInternalCodeRule + .as(artifactId + " should not use internal code from other artifacts") + .check(artifactClasses); + // To view artifacts which do not contain shared internal code, change test log level or + // increase log level of this statement to WARNING + logger.log(Level.INFO, artifactId + " does not contain shared internal code"); + } catch (AssertionError e) { + if (exemptions.contains(artifactId)) { + // To view details, remove from exemptions list + logger.log( + Level.WARNING, artifactId + " contains shared internal code but is temporarily exempt"); + } else { + throw e; + } + } + } + + 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(":", 2); + return Arguments.of(parts[0], parts[1]); + }); + } +}