1plugins { 2 id 'com.github.johnrengelman.shadow' version '7.1.2' 3} 4 5import aQute.bnd.gradle.BundleTaskConvention 6import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar 7import org.codehaus.groovy.runtime.InvokerHelper 8 9apply plugin: 'biz.aQute.bnd.builder' 10 11description = 'Conscrypt: OpenJdk' 12 13// Gradle mostly uses Java os.arch names for architectures which feeds into default 14// targetPlatform names. Notable exception Gradle 6.9.x reports MacOS/ARM as 15// arm-v8. 16// 17// The Maven osdetector plugin (which we recommend to developers) uses different 18// arch names, so that's what we need for artifacts. 19// 20// This class encapsulates both naming schemes as well as other per-platform information 21// about native builds, more of which will migrate in here over time. 22enum NativeBuildInfo { 23 WINDOWS_X86_64("windows", "x86_64"), 24 LINUX_X86_64("linux", "x86_64"), 25 MAC_X86_64("osx", "x86_64") { 26 String libDir() { 27 "build.x86" 28 } 29 }, 30 MAC_AARCH64("osx", "aarch_64") { 31 String libDir() { 32 "build.arm" 33 } 34 }; 35 36 static String buildDir = "FIXME" // See below 37 38 public final String os 39 public final String arch 40 41 // Maps osdetector arch to Gradle equivalent. 42 private static final gradleArchMap = [ 43 "aarch_64": "aarch64", 44 "x86_64" : "x86-64", 45 ] 46 47 NativeBuildInfo(String os, String arch) { 48 this.os = os 49 this.arch = arch 50 } 51 52 // Classifier as generated by Maven osdetector. 53 String mavenClassifier() { 54 "${os}-${arch}" 55 } 56 57 // Gradle equivalent to Maven arch 58 String gradleArch() { 59 gradleArch(arch) 60 } 61 62 // Output directory for native resources 63 String nativeResourcesDir() { 64 "$buildDir/${mavenClassifier()}/native-resources" 65 } 66 67 // Directory for native resources inside final jar. 68 String jarNativeResourcesDir() { 69 nativeResourcesDir() + '/META-INF/native' 70 } 71 72 // Target platform identifier as used by Gradle 73 String targetPlatform() { 74 "${os}_${gradleArch()}" 75 } 76 77 String libDir() { 78 "build64" 79 } 80 81 static String gradleArch(String arch) { 82 gradleArchMap.get(arch) 83 } 84 85 static NativeBuildInfo findForGradle(String os, String arch) { 86 values().find { 87 it.os == os && it.gradleArch() == arch 88 } 89 } 90 91 static NativeBuildInfo find(String os, String arch) { 92 values().find { 93 it.os == os && it.arch == arch 94 } 95 } 96 97 static NativeBuildInfo find(NativePlatform targetPlatform) { 98 String targetOS = targetPlatform.operatingSystem.name 99 String targetArch = targetPlatform.architecture.name 100 def result = findForGradle(targetOS, targetArch) 101 assert result != null : "Unknown target platform: ${targetOS}-${targetArch}" 102 result 103 } 104 105 static findAll(String os) { 106 values().findAll { 107 it.os == os 108 } 109 } 110} 111 112// TODO: There has to be a better way of accessing Gradle properties from Groovy code than this 113NativeBuildInfo.buildDir = "$buildDir" 114 115ext { 116 jniSourceDir = "$rootDir/common/src/jni" 117 assert file("$jniSourceDir").exists() 118 119 // Decide which targets we should build and test 120 nativeBuilds = NativeBuildInfo.findAll("${osdetector.os}") 121 buildToTest = NativeBuildInfo.find("${osdetector.os}", "${osdetector.arch}") 122 123 assert !nativeBuilds.isEmpty() : "No native builds selected." 124 assert buildToTest != null : "No test build selected for os.arch = ${osdetector.arch}" 125 126 // Compatibility with other sub-projects 127 preferredSourceSet = buildToTest.mavenClassifier() 128 preferredNativeFileDir = buildToTest.nativeResourcesDir() 129} 130 131sourceSets { 132 133 main { 134 java { 135 srcDirs += "${rootDir}/common/src/main/java" 136 srcDirs += project(':conscrypt-constants').sourceSets.main.java.srcDirs 137 } 138 resources { 139 srcDirs += "build/generated/resources" 140 } 141 } 142 143 platform { 144 java { 145 srcDirs = [ "src/main/java" ] 146 includes = [ "org/conscrypt/Platform.java" ] 147 } 148 } 149 150 test { 151 java { 152 srcDirs += "${rootDir}/common/src/test/java" 153 } 154 resources { 155 srcDirs += "${rootDir}/common/src/test/resources" 156 // This shouldn't be needed but seems to help IntelliJ locate the native artifact. 157 // srcDirs += preferredNativeFileDir 158 srcDirs += buildToTest.nativeResourcesDir() 159 } 160 } 161 162 // Add the source sets for each of the native builds 163 nativeBuilds.each { nativeBuild -> 164 String sourceSetName = nativeBuild.mavenClassifier() 165 String nativeDir = nativeBuild.nativeResourcesDir() 166 167 // Main sources for the native build 168 "$sourceSetName" { 169 output.dir(nativeDir, builtBy: "copyNativeLib${sourceSetName}") 170 } 171 } 172} 173 174compileJava { 175 dependsOn generateProperties 176} 177 178processResources { 179 dependsOn generateProperties 180} 181 182tasks.register("platformJar", Jar) { 183 from sourceSets.platform.output 184} 185 186tasks.register("testJar", ShadowJar) { 187 classifier = 'tests' 188 configurations = [project.configurations.testRuntimeClasspath] 189 from sourceSets.test.output 190} 191 192if (isExecutableOnPath('cpplint')) { 193 def cpplint = tasks.register("cpplint", Exec) { 194 executable = 'cpplint' 195 196 // TODO(nmittler): Is there a better way of getting the JNI sources? 197 def pattern = ['**/*.cc', '**/*.h'] 198 def sourceFiles = fileTree(dir: jniSourceDir, includes: pattern).asPath.tokenize(':') 199 // Adding roots so that class #ifdefs don't require full path from the project root. 200 args = sourceFiles 201 202 // Capture stderr from the process 203 errorOutput = new ByteArrayOutputStream(); 204 205 // Need to ignore exit value so that doLast will execute. 206 ignoreExitValue = true 207 208 doLast { 209 // Create the report file. 210 def reportDir = file("${buildDir}/cpplint") 211 reportDir.mkdirs(); 212 def reportFile = new File(reportDir, "report.txt") 213 def reportStream = new FileOutputStream(reportFile) 214 215 try { 216 // Check for failure 217 if (execResult != null) { 218 execResult.assertNormalExitValue() 219 } 220 } catch (Exception e) { 221 // The process failed - get the error report from the stderr. 222 String report = errorOutput.toString(); 223 224 // Write the report to the console. 225 System.err.println(report) 226 227 // Also write the report file. 228 reportStream.write(report.bytes); 229 230 // Extension method cpplint.output() can be used to obtain the report 231 ext.output = { 232 return report 233 } 234 235 // Rethrow the exception to terminate the build. 236 throw e; 237 } finally { 238 reportStream.close(); 239 } 240 } 241 } 242 check.dependsOn cpplint 243} 244 245configurations { 246 publicApiDocs 247 platform 248} 249 250artifacts { 251 platform platformJar 252} 253 254apply from: "$rootDir/gradle/publishing.gradle" 255publishing.publications.maven { 256 artifact sourcesJar 257 artifact javadocJar 258} 259 260jar.manifest { 261 attributes ('BoringSSL-Version' : boringSslVersion, 262 'Automatic-Module-Name' : 'org.conscrypt', 263 'Bundle-SymbolicName': 'org.conscrypt', 264 '-exportcontents': 'org.conscrypt.*') 265} 266 267dependencies { 268 // This is used for the @Internal annotation processing in JavaDoc 269 publicApiDocs project(':conscrypt-api-doclet') 270 271 // This is listed as compile-only, but we absorb its contents. 272 compileOnly project(':conscrypt-constants') 273 274 testImplementation project(':conscrypt-constants'), 275 project(path: ':conscrypt-testing', configuration: 'shadow'), 276 libraries.junit, 277 libraries.mockito 278 279 testRuntimeOnly sourceSets["$preferredSourceSet"].output 280 281 platformCompileOnly sourceSets.main.output 282} 283 284nativeBuilds.each { nativeBuild -> 285 // Create the JAR task and add it's output to the published archives for this project 286 addNativeJar(nativeBuild) 287 288 // Build the classes as part of the standard build. 289 classes.dependsOn sourceSets[nativeBuild.mavenClassifier()].classesTaskName 290} 291 292// Adds a JAR task for the native library. 293def addNativeJar(NativeBuildInfo nativeBuild) { 294 // Create a JAR for this configuration and add it to the output archives. 295 SourceSet sourceSet = sourceSets[nativeBuild.mavenClassifier()] 296 def jarTask = tasks.register(sourceSet.jarTaskName, Jar) { Jar t -> 297 // Depend on the regular classes task 298 dependsOn classes 299 manifest = jar.manifest 300 classifier = nativeBuild.mavenClassifier() 301 302 from sourceSet.output + sourceSets.main.output 303 304 // add OSGI headers 305 t.convention.plugins.bundle = new BundleTaskConvention(t) 306 t.doLast { 307 t.buildBundle() 308 } 309 } 310 311 // Add the jar task to the standard build. 312 jar.dependsOn jarTask 313 314 // Add it to the publishing archives list. 315 publishing.publications.maven.artifact jarTask.get() 316} 317 318test { 319 include "org/conscrypt/ConscryptOpenJdkSuite.class" 320} 321 322def testFdSocket = tasks.register("testFdSocket", Test) { 323 include "org/conscrypt/ConscryptOpenJdkSuite.class" 324 InvokerHelper.setProperties(testLogging, test.testLogging.properties) 325 systemProperties = test.systemProperties 326 systemProperty "org.conscrypt.useEngineSocketByDefault", false 327} 328check.dependsOn testFdSocket 329 330// Tests that involve interoperation with the OpenJDK TLS provider (generally to 331// test renegotiation, since we don't support initiating renegotiation but do 332// support peer-initiated renegotiation). The JDK TLS provider doesn't work 333// if Conscrypt is installed as the default provider, so these need to live in 334// a different task than the other tests, most of which need Conscrypt to be 335// installed to function. 336def testInterop = tasks.register("testInterop", Test) { 337 include "org/conscrypt/ConscryptEngineTest.class" 338 include "org/conscrypt/RenegotiationTest.class" 339} 340check.dependsOn testInterop 341 342jacocoTestReport { 343 additionalSourceDirs.from files("$rootDir/openjdk/src/test/java", "$rootDir/common/src/main/java") 344 executionData tasks.withType(Test) 345} 346 347javadoc { 348 dependsOn(configurations.publicApiDocs) 349 // TODO(prb): Update doclet to Java 11. 350 // options.doclet = "org.conscrypt.doclet.FilterDoclet" 351 // options.docletpath = configurations.publicApiDocs.files as List 352} 353 354def jniIncludeDir() { 355 def result = "" 356 java { 357 def jdkHome = javaToolchains.compilerFor(toolchain).get().metadata.getInstallationPath() 358 result = jdkHome.file("include").toString() 359 } 360 result 361} 362 363model { 364 buildTypes { 365 release 366 } 367 368 components { 369 // Builds the JNI library. 370 conscrypt_openjdk_jni(NativeLibrarySpec) { 371 nativeBuilds.each { nativeBuild -> 372 targetPlatform nativeBuild.targetPlatform() 373 } 374 375 sources { 376 cpp { 377 source { 378 srcDirs "$jniSourceDir/main/cpp" 379 include "**/*.cc" 380 } 381 } 382 } 383 384 binaries { 385 // Build the JNI lib as a shared library. 386 withType (SharedLibraryBinarySpec) { 387 cppCompiler.define "CONSCRYPT_OPENJDK" 388 def jdkIncludeDir = jniIncludeDir() 389 def nativeBuild = NativeBuildInfo.find(targetPlatform) 390 String libPath = "$boringsslHome/${nativeBuild.libDir()}" 391 392 if (toolChain in Clang || toolChain in Gcc) { 393 cppCompiler.args "-Wall", 394 "-fPIC", 395 "-O3", 396 "-std=c++17", 397 "-I$jniSourceDir/main/include", 398 "-I$jniSourceDir/unbundled/include", 399 "-I$boringsslIncludeDir", 400 "-I$jdkIncludeDir", 401 "-I$jdkIncludeDir/linux", 402 "-I$jdkIncludeDir/darwin", 403 "-I$jdkIncludeDir/win32" 404 if (rootProject.hasProperty('checkErrorQueue')) { 405 System.out.println("Compiling with error queue checking enabled") 406 cppCompiler.define "CONSCRYPT_CHECK_ERROR_QUEUE" 407 } 408 409 // Static link to BoringSSL 410 linker.args "-O3", 411 "-fvisibility=hidden", 412 "-lpthread", 413 libPath + "/ssl/libssl.a", 414 libPath + "/crypto/libcrypto.a" 415 if (targetPlatform.operatingSystem.isLinux()) { 416 // Static link libstdc++ and libgcc because 417 // they are not available in some restrictive Linux 418 // environments. 419 linker.args "-static-libstdc++", 420 "-static-libgcc" 421 } else { 422 linker.args "-lstdc++" 423 } 424 } else if (toolChain in VisualCpp) { 425 cppCompiler.define "DLL_EXPORT" 426 cppCompiler.define "WIN32_LEAN_AND_MEAN" 427 cppCompiler.define "NOMINMAX" 428 cppCompiler.define "WIN64" 429 cppCompiler.define "_WINDOWS" 430 cppCompiler.define "UNICODE" 431 cppCompiler.define "_UNICODE" 432 cppCompiler.define "NDEBUG" 433 434 cppCompiler.args "/nologo", 435 "/MT", 436 "/WX-", 437 "/Wall", 438 "/O2", 439 "/Oi", 440 "/Ot", 441 "/GL", 442 "/GS", 443 "/Gy", 444 "/fp:precise", 445 "/std:c++17", 446 "-wd4514", // Unreferenced inline function removed 447 "-wd4548", // Expression before comma has no effect 448 "-wd4625", // Copy constructor was implicitly defined as deleted 449 "-wd4626", // Assignment operator was implicitly defined as deleted 450 "-wd4710", // function not inlined 451 "-wd4711", // function inlined 452 "-wd4820", // Extra padding added to struct 453 "-wd4946", // reinterpret_cast used between related classes: 454 "-wd4996", // Thread safety for strerror 455 "-wd5027", // Move assignment operator was implicitly defined as deleted 456 "-I$jniSourceDir/main/include", 457 "-I$jniSourceDir/unbundled/include", 458 "-I$boringsslIncludeDir", 459 "-I$jdkIncludeDir", 460 "-I$jdkIncludeDir/win32" 461 462 // Static link to BoringSSL 463 linker.args "-WX", 464 "ws2_32.lib", 465 "advapi32.lib", 466 "${libPath}\\ssl\\ssl.lib", 467 "${libPath}\\crypto\\crypto.lib" 468 } 469 } 470 471 // Never build a static library. 472 withType(StaticLibraryBinarySpec) { 473 buildable = false 474 } 475 } 476 } 477 } 478 479 tasks { t -> 480 $.binaries.withType(SharedLibraryBinarySpec).each { binary -> 481 def nativeBuild = NativeBuildInfo.find(binary.targetPlatform) 482 def classifier = nativeBuild.mavenClassifier() 483 def source = binary.sharedLibraryFile 484 485 // Copies the native library to a resource location that will be included in the jar. 486 def copyTask = project.tasks.register("copyNativeLib${classifier}", Copy) { 487 dependsOn binary.tasks.link 488 from source 489 // Rename the artifact to include the generated classifier 490 rename '(.+)(\\.[^\\.]+)', "\$1-$classifier\$2" 491 // Everything under will be included in the native jar. 492 into nativeBuild.jarNativeResourcesDir() 493 } 494 processResources { 495 dependsOn copyTask 496 } 497 processTestResources { 498 dependsOn copyTask 499 } 500 501 // Now define a task to strip the release binary (linux only) 502 if (osdetector.os == 'linux' && (!rootProject.hasProperty('nostrip') || 503 !rootProject.nostrip.toBoolean())) { 504 def stripTask = binary.tasks.taskName("strip") 505 project.tasks.register(stripTask as String, Exec) { 506 dependsOn binary.tasks.link 507 executable "strip" 508 args binary.tasks.link.linkedFile.asFile.get() 509 } 510 copyTask.configure { 511 dependsOn stripTask 512 } 513 } 514 } 515 } 516} 517 518boolean isExecutableOnPath(executable) { 519 FilenameFilter filter = new FilenameFilter() { 520 @Override 521 boolean accept(File dir, String name) { 522 return executable.equals(name); 523 } 524 } 525 for(String folder : System.getenv('PATH').split("" + File.pathSeparatorChar)) { 526 File[] files = file(folder).listFiles(filter) 527 if (files != null && files.size() > 0) { 528 return true; 529 } 530 } 531 return false; 532} 533 534