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