plugins { id 'com.github.johnrengelman.shadow' version '2.0.2' } description = 'OpenCensus Agent' def agentPackage = 'io.opencensus.contrib.agent' def agentMainClass = "${agentPackage}.AgentMain" // The package containing the classes that need to be loaded by the bootstrap classloader because // they are used from classes loaded by the bootstrap classloader. def agentBootstrapPackage = "${agentPackage}.bootstrap" def agentBootstrapPackageDir = agentBootstrapPackage.replace('.', '/') + '/' def agentBootstrapClasses = agentBootstrapPackageDir + '**' // The package to which we relocate all third party packages. This avoids any conflicts of the // agent's classes with the app's classes, which are loaded by the same classloader (the system // classloader). def agentRepackaged = "${agentPackage}.deps" dependencies { compileOnly libraries.auto_service compileOnly libraries.grpc_context compileOnly project(':opencensus-api') compile libraries.byte_buddy compile libraries.config compile libraries.findbugs_annotations compile libraries.guava signature 'org.codehaus.mojo.signature:java17:1.0@signature' } jar { manifest { // Set the required manifest attributes for the Java agent, cf. // https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/package-summary.html. attributes 'Premain-Class': agentMainClass attributes 'Can-Retransform-Classes': true } } // Create bootstrap.jar containing the classes that need to be loaded by the bootstrap // classloader. task bootstrapJar(type: Jar) { // Output to 'bootstrap.jar'. baseName = 'bootstrap' version = null from sourceSets.main.output include agentBootstrapClasses } shadowJar.dependsOn bootstrapJar // Bundle the agent's classes and dependencies into a single, self-contained JAR file. shadowJar { // Output to opencensus-contrib-agent-VERSION.jar. classifier = null // Include only the following dependencies (excluding transitive dependencies). dependencies { include(dependency(libraries.byte_buddy)) include(dependency(libraries.config)) include(dependency(libraries.guava)) } // Exclude cruft which still snuck in. exclude 'META-INF/maven/**' exclude agentBootstrapClasses // Relocate third party packages to avoid any conflicts of the agent's classes with the app's // classes, which are loaded by the same classloader (the system classloader). // Byte Buddy: relocate 'net.bytebuddy', agentRepackaged + '.bytebuddy' // Config: relocate 'com.typesafe.config', agentRepackaged + '.config' // Guava: relocate 'com.google.common', agentRepackaged + '.guava' relocate 'com.google.thirdparty.publicsuffix', agentRepackaged + '.publicsuffix' doLast { def agentPackageDir = agentPackage.replace('.', '/') + '/' def agentBootstrapJar = agentPackageDir + 'bootstrap.jar' // Bundle bootstrap.jar. ant.jar(update: 'true', destfile: shadowJar.archivePath) { mappedresources { fileset(file: bootstrapJar.archivePath) globmapper(from: '*', to: agentBootstrapJar) } } // Assert that there's nothing obviously wrong with the JAR's contents. new java.util.zip.ZipFile(shadowJar.archivePath).withCloseable { // Must have bundled the bootstrap.jar. assert it.entries().any { it.name == agentBootstrapJar } it.entries().each { entry -> // Must not contain anything outside of ${agentPackage}, ... assert entry.name.startsWith(agentPackageDir) || // ... except for the expected entries. [ agentPackageDir, 'META-INF/MANIFEST.MF', 'META-INF/services/io.opencensus.contrib.agent.instrumentation.Instrumenter', 'reference.conf', ].any { entry.isDirectory() ? it.startsWith(entry.name) : it == entry.name } // Also, should not have the bootstrap classes. assert !entry.name.startsWith(agentBootstrapPackageDir) } } } } jar.finalizedBy shadowJar // TODO(stschmidt): Proguard-shrink the agent JAR. // Integration tests. The setup was initially based on // https://www.petrikainulainen.net/programming/gradle/getting-started-with-gradle-integration-testing/. // We run the same suite of integration tests on different Java versions with the agent enabled. // The JAVA_HOMES environment variable lists the home directories of the Java installations used // for integration testing. // The default JAR has been replaced with a self-contained JAR by the shadowJar task. Therefore, // remove all declared dependencies from the generated Maven POM for said JAR. uploadArchives { repositories { mavenDeployer { pom.whenConfigured { dependencies = [] } } } } sourceSets { integrationTest { java { compileClasspath += main.output + test.output runtimeClasspath += main.output + test.output srcDir file('src/integration-test/java') } resources.srcDir file('src/integration-test/resources') } } configurations { integrationTestCompile.extendsFrom testCompile integrationTestRuntime.extendsFrom testRuntime } dependencies { integrationTestCompile project(':opencensus-api') integrationTestCompile project(':opencensus-testing') integrationTestRuntime libraries.grpc_context integrationTestRuntime project(':opencensus-impl-lite') } // Disable checkstyle for integration tests if not java8. checkstyleIntegrationTest.enabled = JavaVersion.current().isJava8Compatible() // Disable findbugs for integration tests, too. findbugsIntegrationTest.enabled = false def javaExecutables = (System.getenv('JAVA_HOMES') ?: '') .tokenize(File.pathSeparator) .plus(System.getProperty('java.home')) .collect { org.apache.tools.ant.taskdefs.condition.Os.isFamily( org.apache.tools.ant.taskdefs.condition.Os.FAMILY_WINDOWS) ? "${it}/bin/java.exe" : "${it}/bin/java" } .collect { new File(it).getCanonicalPath() } .unique() assert javaExecutables.size > 0 : 'No Java executables found for running integration tests' task integrationTest javaExecutables.eachWithIndex { javaExecutable, index -> def perVersionIntegrationTest = task("integrationTest_${index}", type: Test) { testLogging { // Let Gradle output the stdout and stderr from tests, too. This is useful for investigating // test failures on Travis, where we can't view Gradle's test reports. showStandardStreams = true // Include the exception message and full stacktrace for failed tests. exceptionFormat 'full' } dependsOn shadowJar testClassesDirs = sourceSets.integrationTest.output.classesDirs classpath = sourceSets.integrationTest.runtimeClasspath executable = javaExecutable // The JaCoCo agent must be specified first so that it can instrument our agent. // This is a work around for the issue that the JaCoCo agent is added last, cf. // https://discuss.gradle.org/t/jacoco-gradle-adds-the-agent-last-to-jvm-args/7124. doFirst { jvmArgs jacoco.asJvmArg // JaCoCo agent first. jvmArgs "-javaagent:${shadowJar.archivePath}" // Our agent second. jacoco.enabled = false // Don't add the JaCoCo agent again. } doFirst { logger.lifecycle("Running integration tests using ${javaExecutable}.") } } integrationTest.dependsOn perVersionIntegrationTest } check.dependsOn integrationTest integrationTest.mustRunAfter test // Merge JaCoCo's execution data from all tests into the main test's execution data file. task jacocoMerge(type: JacocoMerge) { tasks.withType(Test).each { testTask -> dependsOn testTask executionData testTask.jacoco.destinationFile } doLast { destinationFile.renameTo test.jacoco.destinationFile } } jacocoTestReport.dependsOn jacocoMerge // JMH benchmarks dependencies { jmh libraries.grpc_context } // Make the agent JAR available using a fixed file name so that we don't have to modify the JMH // benchmarks whenever the version changes. task agentJar(type: Copy) { dependsOn shadowJar from shadowJar.archivePath into libsDir rename { 'agent.jar' } } jmhJar.dependsOn agentJar jmhJar.dependsOn integrationTest