aboutsummaryrefslogtreecommitdiff
path: root/contrib/agent/build.gradle
blob: 11271a42e0b41b9a4aa5e63b656daa73b03c5151 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
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