Prepare a single test bootstrap jar at top level. (#1635)

* Prepare a single test bootstrap jar at top level.

* Apply suggestions from code review

Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>

* Add comment

* Use buildDir instead of build

* Testing SDK also in bootstrap

* Remove sdk from testing bootstrap loader

Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>
This commit is contained in:
Anuraag Agrawal 2020-11-16 11:25:40 +09:00 committed by GitHub
parent 8e48aa2025
commit 9ac34bce67
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 44 additions and 114 deletions

View File

@ -14,7 +14,7 @@ plugins {
id 'com.dorongold.task-tree' version '1.5'
id "com.github.johnrengelman.shadow" version "6.0.0"
id "com.github.johnrengelman.shadow" version "6.1.0"
id "com.diffplug.spotless" version "5.6.1"
id "com.github.spotbugs" version "4.5.1"

View File

@ -5,21 +5,13 @@
package io.opentelemetry.instrumentation.gradle;
import static java.util.stream.Collectors.toList;
import java.io.File;
import java.util.Arrays;
import java.util.concurrent.Callable;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.file.DuplicatesStrategy;
import org.gradle.api.plugins.JavaLibraryPlugin;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.api.tasks.testing.Test;
import org.gradle.jvm.tasks.Jar;
import org.gradle.process.CommandLineArgumentProvider;
/**
@ -29,106 +21,22 @@ import org.gradle.process.CommandLineArgumentProvider;
// TODO(anuraaga): Migrate more build logic into this plugin to avoid having two places for it.
public class AutoInstrumentationPlugin implements Plugin<Project> {
/**
* An exact copy of {@code
* io.opentelemetry.javaagent.tooling.Constants#BOOTSTRAP_PACKAGE_PREFIXES}. We can't reference it
* directly since this file needs to be compiled before the other packages.
*/
public static final String[] BOOTSTRAP_PACKAGE_PREFIXES_COPY = {
"io.opentelemetry.javaagent.common.exec",
"io.opentelemetry.javaagent.slf4j",
"io.opentelemetry.javaagent.bootstrap",
"io.opentelemetry.javaagent.shaded",
"io.opentelemetry.javaagent.instrumentation.api",
};
// Aditional classes we need only for tests and aren't shared with the agent business logic.
private static final String[] TEST_BOOTSTRAP_PREFIXES;
static {
String[] testBS = {
"io.opentelemetry.instrumentation.api",
"io.opentelemetry.api", // OpenTelemetry API
"io.opentelemetry.context", // OpenTelemetry API
"org.slf4j",
"ch.qos.logback",
// Tomcat's servlet classes must be on boostrap
// when running tomcat test
"javax.servlet.ServletContainerInitializer",
"javax.servlet.ServletContext"
};
TEST_BOOTSTRAP_PREFIXES =
Arrays.copyOf(
BOOTSTRAP_PACKAGE_PREFIXES_COPY,
BOOTSTRAP_PACKAGE_PREFIXES_COPY.length + testBS.length);
System.arraycopy(
testBS, 0, TEST_BOOTSTRAP_PREFIXES, BOOTSTRAP_PACKAGE_PREFIXES_COPY.length, testBS.length);
for (int i = 0; i < TEST_BOOTSTRAP_PREFIXES.length; i++) {
TEST_BOOTSTRAP_PREFIXES[i] = TEST_BOOTSTRAP_PREFIXES[i].replace('.', '/');
}
}
@Override
public void apply(Project project) {
project.getPlugins().apply(JavaLibraryPlugin.class);
project
.getTasks()
.withType(
Test.class,
task -> {
TaskProvider<Jar> bootstrapJar =
project.getTasks().register(task.getName() + "BootstrapJar", Jar.class);
Configuration testClasspath =
project.getConfigurations().findByName(task.getName() + "RuntimeClasspath");
if (testClasspath == null) {
// Same classpath as default test task
testClasspath =
project
.getConfigurations()
.findByName(JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME);
}
String bootstrapJarName = task.getName() + "-bootstrap.jar";
Configuration testClasspath0 = testClasspath;
bootstrapJar.configure(
jar -> {
jar.dependsOn(testClasspath0.getBuildDependencies());
jar.getArchiveFileName().set(bootstrapJarName);
jar.setIncludeEmptyDirs(false);
// Classpath is ordered in priority, but later writes into the JAR would take
// priority, so we exclude the later ones (we need this to make sure logback is
// picked up).
jar.setDuplicatesStrategy(DuplicatesStrategy.EXCLUDE);
jar.from(
project.files(
// Needs to be a Callable so it's executed lazily at runtime, instead of
// configuration time where the classpath may still be getting built up.
(Callable<?>)
() ->
testClasspath0.resolve().stream()
.filter(
file ->
!file.isDirectory()
&& file.getName().endsWith(".jar"))
.map(project::zipTree)
.collect(toList())));
jar.eachFile(
file -> {
if (!isBootstrapClass(file.getPath())) {
file.exclude();
}
});
});
task.dependsOn(bootstrapJar);
task.getJvmArgumentProviders()
.add(
new InstrumentationTestArgs(
new File(project.getBuildDir(), "libs/" + bootstrapJarName)));
task.dependsOn(":testing-bootstrap:shadowJar");
File testingBootstrapJar =
new File(
project.project(":testing-bootstrap").getBuildDir(),
"libs/testing-bootstrap.jar");
// Make sure tests get rerun if the contents of the testing-bootstrap.jar change
task.getInputs().property("testing-bootstrap-jar", testingBootstrapJar);
task.getJvmArgumentProviders().add(new InstrumentationTestArgs(testingBootstrapJar));
});
}
@ -150,13 +58,4 @@ public class AutoInstrumentationPlugin implements Plugin<Project> {
"-Xbootclasspath/a:" + bootstrapJar.getAbsolutePath(), "-Dnet.bytebuddy.raw=true");
}
}
private static boolean isBootstrapClass(String filePath) {
for (String testBootstrapPrefix : TEST_BOOTSTRAP_PREFIXES) {
if (filePath.startsWith(testBootstrapPrefix)) {
return true;
}
}
return false;
}
}

View File

@ -22,10 +22,10 @@ shadowJar {
mergeServiceFiles()
relocate "com.google", "io.opentelemetry.javaagent.shaded.com.google"
relocate "javax.annotation", "io.opentelemetry.javaagent.shaded.javax.annotation"
relocate "org.checkerframework", "io.opentelemetry.javaagent.shaded.org.checkerframework"
relocate "org.codehaus", "io.opentelemetry.javaagent.shaded.org.codehaus"
relocate "com.google", "shaded.for.testing.com.google"
relocate "javax.annotation", "shaded.for.testing.javax.annotation"
relocate "org.checkerframework", "shaded.for.testing.org.checkerframework"
relocate "org.codehaus", "shaded.for.testing.org.codehaus"
exclude 'META-INF/maven/**'
exclude 'org/codehaus/mojo/animal_sniffer/**' // this is Java 8 bytecode

View File

@ -47,6 +47,7 @@ include ':instrumentation-api'
include ':javaagent-api'
// misc
include ':testing-bootstrap'
include ':testing-common'
include ':utils:test-utils'

View File

@ -0,0 +1,30 @@
plugins {
id "com.github.johnrengelman.shadow"
}
apply from: "$rootDir/gradle/java.gradle"
// Depend on all libraries that are in the bootstrap classloader when running the agent. When
// running tests, we simulate this by adding the jar produced by this project to the bootstrap
// classpath.
dependencies {
implementation project(":instrumentation-api")
implementation project(":javaagent-bootstrap")
implementation deps.opentelemetryApi
implementation deps.opentelemetryContext
implementation deps.slf4j
implementation "ch.qos.logback:logback-classic:${versions.logback}"
}
shadowJar {
archiveFileName = "testing-bootstrap.jar"
// need to exclude these logback classes from the bootstrap jar, otherwise tomcat will find them
// and try to load them from the bootstrap class loader, which will fail with NoClassDefFoundError
// since their super classes are servlet classes which are not in the bootstrap class loader
exclude "ch/qos/logback/classic/servlet/LogbackServletContainerInitializer.class"
exclude "ch/qos/logback/classic/servlet/LogbackServletContextListener.class"
exclude "META-INF/services/javax.servlet.ServletContainerInitializer"
}