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 180sourcesJar { 181 dependsOn generateProperties 182 dependsOn ':conscrypt-constants:runGen' 183} 184 185tasks.register("platformJar", Jar) { 186 from sourceSets.platform.output 187} 188 189tasks.register("testJar", ShadowJar) { 190 archiveClassifier = 'tests' 191 configurations = [project.configurations.testRuntimeClasspath] 192 from sourceSets.test.output 193} 194 195if (isExecutableOnPath('cpplint')) { 196 def cpplint = tasks.register("cpplint", Exec) { 197 executable = 'cpplint' 198 199 // TODO(nmittler): Is there a better way of getting the JNI sources? 200 def pattern = ['**/*.cc', '**/*.h'] 201 def sourceFiles = fileTree(dir: jniSourceDir, includes: pattern).asPath.tokenize(':') 202 // Adding roots so that class #ifdefs don't require full path from the project root. 203 args = sourceFiles 204 205 // Capture stderr from the process 206 errorOutput = new ByteArrayOutputStream() 207 208 // Need to ignore exit value so that doLast will execute. 209 ignoreExitValue = true 210 211 doLast { 212 // Create the report file. 213 def reportDir = file("${buildDir}/cpplint") 214 reportDir.mkdirs() 215 def reportFile = new File(reportDir, "report.txt") 216 def reportStream = new FileOutputStream(reportFile) 217 218 try { 219 // Check for failure 220 if (execResult != null) { 221 execResult.assertNormalExitValue() 222 } 223 } catch (Exception e) { 224 // The process failed - get the error report from the stderr. 225 String report = errorOutput.toString() 226 227 // Write the report to the console. 228 System.err.println(report) 229 230 // Also write the report file. 231 reportStream.write(report.bytes) 232 233 // Extension method cpplint.output() can be used to obtain the report 234 ext.output = { 235 return report 236 } 237 238 // Rethrow the exception to terminate the build. 239 throw e 240 } finally { 241 reportStream.close() 242 } 243 } 244 } 245 check.dependsOn cpplint 246} 247 248configurations { 249 publicApiDocs 250 platform 251} 252 253artifacts { 254 platform platformJar 255} 256 257apply from: "$rootDir/gradle/publishing.gradle" 258publishing.publications.maven { 259 artifact sourcesJar 260 artifact javadocJar 261} 262 263jar.manifest { 264 attributes ('BoringSSL-Version' : boringSslVersion, 265 'Automatic-Module-Name' : 'org.conscrypt', 266 'Bundle-SymbolicName': 'org.conscrypt', 267 '-exportcontents': 'org.conscrypt.*') 268} 269 270dependencies { 271 // This is used for the @Internal annotation processing in JavaDoc 272 publicApiDocs project(':conscrypt-api-doclet') 273 274 // This is listed as compile-only, but we absorb its contents. 275 compileOnly project(':conscrypt-constants') 276 277 testImplementation project(':conscrypt-constants'), 278 project(path: ':conscrypt-testing', configuration: 'shadow'), 279 libs.junit, 280 libs.mockito 281 282 testRuntimeOnly sourceSets["$preferredSourceSet"].output 283 284 platformCompileOnly sourceSets.main.output 285} 286 287nativeBuilds.each { nativeBuild -> 288 // Create the JAR task and add it's output to the published archives for this project 289 addNativeJar(nativeBuild) 290 291 // Build the classes as part of the standard build. 292 classes.dependsOn sourceSets[nativeBuild.mavenClassifier()].classesTaskName 293} 294 295// Adds a JAR task for the native library. 296def addNativeJar(NativeBuildInfo nativeBuild) { 297 // Create a JAR for this configuration and add it to the output archives. 298 SourceSet sourceSet = sourceSets[nativeBuild.mavenClassifier()] 299 def jarTask = tasks.register(sourceSet.jarTaskName, Jar) { Jar t -> 300 // Depend on the regular classes task 301 dependsOn classes 302 manifest = jar.manifest 303 archiveClassifier = nativeBuild.mavenClassifier() 304 305 from sourceSet.output + sourceSets.main.output 306 307 // add OSGI headers 308 t.convention.plugins.bundle = new BundleTaskConvention(t) 309 t.doLast { 310 t.buildBundle() 311 } 312 } 313 314 // Add the jar task to the standard build. 315 jar.dependsOn jarTask 316 317 // Add it to the publishing archives list. 318 publishing.publications.maven.artifact jarTask.get() 319} 320 321test { 322 include "org/conscrypt/ConscryptOpenJdkSuite.class" 323} 324 325def testFdSocket = tasks.register("testFdSocket", Test) { 326 include "org/conscrypt/ConscryptOpenJdkSuite.class" 327 InvokerHelper.setProperties(testLogging, test.testLogging.properties) 328 systemProperties = test.systemProperties 329 systemProperty "org.conscrypt.useEngineSocketByDefault", false 330} 331 332// Tests that involve interoperation with the OpenJDK TLS provider (generally to 333// test renegotiation, since we don't support initiating renegotiation but do 334// support peer-initiated renegotiation). The JDK TLS provider doesn't work 335// if Conscrypt is installed as the default provider, so these need to live in 336// a different task than the other tests, most of which need Conscrypt to be 337// installed to function. 338def testInterop = tasks.register("testInterop", Test) { 339 include "org/conscrypt/ConscryptEngineTest.class" 340 include "org/conscrypt/RenegotiationTest.class" 341} 342check.dependsOn testInterop 343 344jacocoTestReport { 345 additionalSourceDirs.from files("$rootDir/openjdk/src/test/java", "$rootDir/common/src/main/java") 346 executionData tasks.withType(Test) 347 dependsOn test 348} 349 350javadoc { 351 dependsOn configurations.publicApiDocs 352 options { 353 showFromPublic() 354 encoding = 'UTF-8' 355 doclet = 'org.conscrypt.doclet.FilterDoclet' 356 links = ['https://docs.oracle.com/en/java/javase/21/docs/api/java.base/'] 357 docletpath = configurations.publicApiDocs.files as List 358 } 359 failOnError false 360 361 doLast { 362 copy { 363 from "$rootDir/api-doclet/src/main/resources/styles.css" 364 into "$buildDir/docs/javadoc" 365 } 366 } 367} 368 369def jniIncludeDir() { 370 def result = "" 371 java { 372 def jdkHome = javaToolchains.compilerFor(toolchain).get().metadata.getInstallationPath() 373 result = jdkHome.file("include").toString() 374 } 375 result 376} 377 378model { 379 buildTypes { 380 release 381 } 382 383 components { 384 // Builds the JNI library. 385 conscrypt_openjdk_jni(NativeLibrarySpec) { 386 nativeBuilds.each { nativeBuild -> 387 targetPlatform nativeBuild.targetPlatform() 388 } 389 390 sources { 391 cpp { 392 source { 393 srcDirs "$jniSourceDir/main/cpp" 394 include "**/*.cc" 395 } 396 } 397 } 398 399 binaries { 400 // Build the JNI lib as a shared library. 401 withType (SharedLibraryBinarySpec) { 402 cppCompiler.define "CONSCRYPT_OPENJDK" 403 def jdkIncludeDir = jniIncludeDir() 404 def nativeBuild = NativeBuildInfo.find(targetPlatform) 405 String libPath = "$boringsslHome/${nativeBuild.libDir()}" 406 407 if (toolChain in Clang || toolChain in Gcc) { 408 cppCompiler.args "-Wall", 409 "-fPIC", 410 "-O3", 411 "-std=c++17", 412 "-I$jniSourceDir/main/include", 413 "-I$jniSourceDir/unbundled/include", 414 "-I$boringsslIncludeDir", 415 "-I$jdkIncludeDir", 416 "-I$jdkIncludeDir/linux", 417 "-I$jdkIncludeDir/darwin", 418 "-I$jdkIncludeDir/win32" 419 if (rootProject.hasProperty('checkErrorQueue')) { 420 System.out.println("Compiling with error queue checking enabled") 421 cppCompiler.define "CONSCRYPT_CHECK_ERROR_QUEUE" 422 } 423 424 // Static link to BoringSSL 425 linker.args "-O3", 426 "-fvisibility=hidden", 427 "-lpthread", 428 libPath + "/ssl/libssl.a", 429 libPath + "/crypto/libcrypto.a" 430 if (targetPlatform.operatingSystem.isLinux()) { 431 // Static link libstdc++ and libgcc because 432 // they are not available in some restrictive Linux 433 // environments. 434 linker.args "-static-libstdc++", 435 "-static-libgcc" 436 } else { 437 linker.args "-lstdc++" 438 } 439 } else if (toolChain in VisualCpp) { 440 cppCompiler.define "DLL_EXPORT" 441 cppCompiler.define "WIN32_LEAN_AND_MEAN" 442 cppCompiler.define "NOMINMAX" 443 cppCompiler.define "WIN64" 444 cppCompiler.define "_WINDOWS" 445 cppCompiler.define "UNICODE" 446 cppCompiler.define "_UNICODE" 447 cppCompiler.define "NDEBUG" 448 449 cppCompiler.args "/nologo", 450 "/MT", 451 "/WX-", 452 "/Wall", 453 "/O2", 454 "/Oi", 455 "/Ot", 456 "/GL", 457 "/GS", 458 "/Gy", 459 "/fp:precise", 460 "/std:c++17", 461 "-wd4514", // Unreferenced inline function removed 462 "-wd4548", // Expression before comma has no effect 463 "-wd4625", // Copy constructor was implicitly defined as deleted 464 "-wd4626", // Assignment operator was implicitly defined as deleted 465 "-wd4710", // function not inlined 466 "-wd4711", // function inlined 467 "-wd4820", // Extra padding added to struct 468 "-wd4946", // reinterpret_cast used between related classes: 469 "-wd4996", // Thread safety for strerror 470 "-wd5027", // Move assignment operator was implicitly defined as deleted 471 "-I$jniSourceDir/main/include", 472 "-I$jniSourceDir/unbundled/include", 473 "-I$boringsslIncludeDir", 474 "-I$jdkIncludeDir", 475 "-I$jdkIncludeDir/win32" 476 477 // Static link to BoringSSL 478 linker.args "-WX", 479 "ws2_32.lib", 480 "advapi32.lib", 481 "${libPath}\\ssl\\ssl.lib", 482 "${libPath}\\crypto\\crypto.lib" 483 } 484 } 485 486 // Never build a static library. 487 withType(StaticLibraryBinarySpec) { 488 buildable = false 489 } 490 } 491 } 492 } 493 494 tasks { t -> 495 $.binaries.withType(SharedLibraryBinarySpec).each { binary -> 496 def nativeBuild = NativeBuildInfo.find(binary.targetPlatform) 497 def classifier = nativeBuild.mavenClassifier() 498 def source = binary.sharedLibraryFile 499 500 // Copies the native library to a resource location that will be included in the jar. 501 def copyTask = project.tasks.register("copyNativeLib${classifier}", Copy) { 502 dependsOn binary.tasks.link 503 from source 504 // Rename the artifact to include the generated classifier 505 rename '(.+)(\\.[^\\.]+)', "\$1-$classifier\$2" 506 // Everything under will be included in the native jar. 507 into nativeBuild.jarNativeResourcesDir() 508 } 509 processResources { 510 dependsOn copyTask 511 } 512 processTestResources { 513 dependsOn copyTask 514 } 515 516 // Now define a task to strip the release binary (linux only) 517 if (osdetector.os == 'linux' && (!rootProject.hasProperty('nostrip') || 518 !rootProject.nostrip.toBoolean())) { 519 def stripTask = binary.tasks.taskName("strip") 520 project.tasks.register(stripTask as String, Exec) { 521 dependsOn binary.tasks.link 522 executable "strip" 523 args binary.tasks.link.linkedFile.asFile.get() 524 } 525 copyTask.configure { 526 dependsOn stripTask 527 } 528 } 529 } 530 } 531} 532 533boolean isExecutableOnPath(executable) { 534 FilenameFilter filter = new FilenameFilter() { 535 @Override 536 boolean accept(File dir, String name) { 537 return executable == name 538 } 539 } 540 for(String folder : System.getenv('PATH').split("" + File.pathSeparatorChar)) { 541 File[] files = file(folder).listFiles(filter) 542 if (files != null && files.size() > 0) { 543 return true 544 } 545 } 546 return false 547} 548