plugins { alias libs.plugins.bnd alias libs.plugins.shadow } import aQute.bnd.gradle.BundleTaskConvention import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import org.codehaus.groovy.runtime.InvokerHelper description = 'Conscrypt: OpenJdk' // Gradle mostly uses Java os.arch names for architectures which feeds into default // targetPlatform names. Notable exception Gradle 6.9.x reports MacOS/ARM as // arm-v8. // // The Maven osdetector plugin (which we recommend to developers) uses different // arch names, so that's what we need for artifacts. // // This class encapsulates both naming schemes as well as other per-platform information // about native builds, more of which will migrate in here over time. enum NativeBuildInfo { WINDOWS_X86_64("windows", "x86_64"), LINUX_X86_64("linux", "x86_64"), MAC_X86_64("osx", "x86_64") { String libDir() { "build.x86" } }, MAC_AARCH64("osx", "aarch_64") { String libDir() { "build.arm" } }; static String buildDir = "FIXME" // See below public final String os public final String arch // Maps osdetector arch to Gradle equivalent. private static final gradleArchMap = [ "aarch_64": "aarch64", "x86_64" : "x86-64", ] NativeBuildInfo(String os, String arch) { this.os = os this.arch = arch } // Classifier as generated by Maven osdetector. String mavenClassifier() { "${os}-${arch}" } // Gradle equivalent to Maven arch String gradleArch() { gradleArch(arch) } // Output directory for native resources String nativeResourcesDir() { "$buildDir/${mavenClassifier()}/native-resources" } // Directory for native resources inside final jar. String jarNativeResourcesDir() { nativeResourcesDir() + '/META-INF/native' } // Target platform identifier as used by Gradle String targetPlatform() { "${os}_${gradleArch()}" } String libDir() { "build64" } static String gradleArch(String arch) { gradleArchMap.get(arch) } static NativeBuildInfo findForGradle(String os, String arch) { values().find { it.os == os && it.gradleArch() == arch } } static NativeBuildInfo find(String os, String arch) { values().find { it.os == os && it.arch == arch } } static NativeBuildInfo find(NativePlatform targetPlatform) { String targetOS = targetPlatform.operatingSystem.name String targetArch = targetPlatform.architecture.name def result = findForGradle(targetOS, targetArch) assert result != null : "Unknown target platform: ${targetOS}-${targetArch}" result } static findAll(String os) { values().findAll { it.os == os } } } // TODO: There has to be a better way of accessing Gradle properties from Groovy code than this NativeBuildInfo.buildDir = "$buildDir" ext { jniSourceDir = "$rootDir/common/src/jni" assert file("$jniSourceDir").exists() // Decide which targets we should build and test nativeBuilds = NativeBuildInfo.findAll("${osdetector.os}") buildToTest = NativeBuildInfo.find("${osdetector.os}", "${osdetector.arch}") assert !nativeBuilds.isEmpty() : "No native builds selected." assert buildToTest != null : "No test build selected for os.arch = ${osdetector.arch}" // Compatibility with other sub-projects preferredSourceSet = buildToTest.mavenClassifier() preferredNativeFileDir = buildToTest.nativeResourcesDir() } sourceSets { main { java { srcDirs += "${rootDir}/common/src/main/java" srcDirs += project(':conscrypt-constants').sourceSets.main.java.srcDirs } resources { srcDirs += "build/generated/resources" } } platform { java { srcDirs = [ "src/main/java" ] includes = [ "org/conscrypt/Platform.java" ] } } test { java { srcDirs += "${rootDir}/common/src/test/java" } resources { srcDirs += "${rootDir}/common/src/test/resources" // This shouldn't be needed but seems to help IntelliJ locate the native artifact. // srcDirs += preferredNativeFileDir srcDirs += buildToTest.nativeResourcesDir() } } // Add the source sets for each of the native builds nativeBuilds.each { nativeBuild -> String sourceSetName = nativeBuild.mavenClassifier() String nativeDir = nativeBuild.nativeResourcesDir() // Main sources for the native build "$sourceSetName" { output.dir(nativeDir, builtBy: "copyNativeLib${sourceSetName}") } } } compileJava { dependsOn generateProperties } processResources { dependsOn generateProperties } tasks.register("platformJar", Jar) { from sourceSets.platform.output } tasks.register("testJar", ShadowJar) { archiveClassifier = 'tests' configurations = [project.configurations.testRuntimeClasspath] from sourceSets.test.output } if (isExecutableOnPath('cpplint')) { def cpplint = tasks.register("cpplint", Exec) { executable = 'cpplint' // TODO(nmittler): Is there a better way of getting the JNI sources? def pattern = ['**/*.cc', '**/*.h'] def sourceFiles = fileTree(dir: jniSourceDir, includes: pattern).asPath.tokenize(':') // Adding roots so that class #ifdefs don't require full path from the project root. args = sourceFiles // Capture stderr from the process errorOutput = new ByteArrayOutputStream() // Need to ignore exit value so that doLast will execute. ignoreExitValue = true doLast { // Create the report file. def reportDir = file("${buildDir}/cpplint") reportDir.mkdirs() def reportFile = new File(reportDir, "report.txt") def reportStream = new FileOutputStream(reportFile) try { // Check for failure if (execResult != null) { execResult.assertNormalExitValue() } } catch (Exception e) { // The process failed - get the error report from the stderr. String report = errorOutput.toString() // Write the report to the console. System.err.println(report) // Also write the report file. reportStream.write(report.bytes) // Extension method cpplint.output() can be used to obtain the report ext.output = { return report } // Rethrow the exception to terminate the build. throw e } finally { reportStream.close() } } } check.dependsOn cpplint } configurations { publicApiDocs platform } artifacts { platform platformJar } apply from: "$rootDir/gradle/publishing.gradle" publishing.publications.maven { artifact sourcesJar artifact javadocJar } jar.manifest { attributes ('BoringSSL-Version' : boringSslVersion, 'Automatic-Module-Name' : 'org.conscrypt', 'Bundle-SymbolicName': 'org.conscrypt', '-exportcontents': 'org.conscrypt.*') } dependencies { // This is used for the @Internal annotation processing in JavaDoc publicApiDocs project(':conscrypt-api-doclet') // This is listed as compile-only, but we absorb its contents. compileOnly project(':conscrypt-constants') testImplementation project(':conscrypt-constants'), project(path: ':conscrypt-testing', configuration: 'shadow'), libs.junit, libs.mockito testRuntimeOnly sourceSets["$preferredSourceSet"].output platformCompileOnly sourceSets.main.output } nativeBuilds.each { nativeBuild -> // Create the JAR task and add it's output to the published archives for this project addNativeJar(nativeBuild) // Build the classes as part of the standard build. classes.dependsOn sourceSets[nativeBuild.mavenClassifier()].classesTaskName } // Adds a JAR task for the native library. def addNativeJar(NativeBuildInfo nativeBuild) { // Create a JAR for this configuration and add it to the output archives. SourceSet sourceSet = sourceSets[nativeBuild.mavenClassifier()] def jarTask = tasks.register(sourceSet.jarTaskName, Jar) { Jar t -> // Depend on the regular classes task dependsOn classes manifest = jar.manifest archiveClassifier = nativeBuild.mavenClassifier() from sourceSet.output + sourceSets.main.output // add OSGI headers t.convention.plugins.bundle = new BundleTaskConvention(t) t.doLast { t.buildBundle() } } // Add the jar task to the standard build. jar.dependsOn jarTask // Add it to the publishing archives list. publishing.publications.maven.artifact jarTask.get() } test { include "org/conscrypt/ConscryptOpenJdkSuite.class" } def testFdSocket = tasks.register("testFdSocket", Test) { include "org/conscrypt/ConscryptOpenJdkSuite.class" InvokerHelper.setProperties(testLogging, test.testLogging.properties) systemProperties = test.systemProperties systemProperty "org.conscrypt.useEngineSocketByDefault", false } check.dependsOn testFdSocket // Tests that involve interoperation with the OpenJDK TLS provider (generally to // test renegotiation, since we don't support initiating renegotiation but do // support peer-initiated renegotiation). The JDK TLS provider doesn't work // if Conscrypt is installed as the default provider, so these need to live in // a different task than the other tests, most of which need Conscrypt to be // installed to function. def testInterop = tasks.register("testInterop", Test) { include "org/conscrypt/ConscryptEngineTest.class" include "org/conscrypt/RenegotiationTest.class" } check.dependsOn testInterop jacocoTestReport { additionalSourceDirs.from files("$rootDir/openjdk/src/test/java", "$rootDir/common/src/main/java") executionData tasks.withType(Test) dependsOn test } javadoc { dependsOn configurations.publicApiDocs options { showFromPublic() encoding = 'UTF-8' doclet = 'org.conscrypt.doclet.FilterDoclet' links = ['https://docs.oracle.com/en/java/javase/21/docs/api/java.base/'] docletpath = configurations.publicApiDocs.files as List } failOnError false doLast { copy { from "$rootDir/api-doclet/src/main/resources/styles.css" into "$buildDir/docs/javadoc" } } } def jniIncludeDir() { def result = "" java { def jdkHome = javaToolchains.compilerFor(toolchain).get().metadata.getInstallationPath() result = jdkHome.file("include").toString() } result } model { buildTypes { release } components { // Builds the JNI library. conscrypt_openjdk_jni(NativeLibrarySpec) { nativeBuilds.each { nativeBuild -> targetPlatform nativeBuild.targetPlatform() } sources { cpp { source { srcDirs "$jniSourceDir/main/cpp" include "**/*.cc" } } } binaries { // Build the JNI lib as a shared library. withType (SharedLibraryBinarySpec) { cppCompiler.define "CONSCRYPT_OPENJDK" def jdkIncludeDir = jniIncludeDir() def nativeBuild = NativeBuildInfo.find(targetPlatform) String libPath = "$boringsslHome/${nativeBuild.libDir()}" if (toolChain in Clang || toolChain in Gcc) { cppCompiler.args "-Wall", "-fPIC", "-O3", "-std=c++17", "-I$jniSourceDir/main/include", "-I$jniSourceDir/unbundled/include", "-I$boringsslIncludeDir", "-I$jdkIncludeDir", "-I$jdkIncludeDir/linux", "-I$jdkIncludeDir/darwin", "-I$jdkIncludeDir/win32" if (rootProject.hasProperty('checkErrorQueue')) { System.out.println("Compiling with error queue checking enabled") cppCompiler.define "CONSCRYPT_CHECK_ERROR_QUEUE" } // Static link to BoringSSL linker.args "-O3", "-fvisibility=hidden", "-lpthread", libPath + "/ssl/libssl.a", libPath + "/crypto/libcrypto.a" if (targetPlatform.operatingSystem.isLinux()) { // Static link libstdc++ and libgcc because // they are not available in some restrictive Linux // environments. linker.args "-static-libstdc++", "-static-libgcc" } else { linker.args "-lstdc++" } } else if (toolChain in VisualCpp) { cppCompiler.define "DLL_EXPORT" cppCompiler.define "WIN32_LEAN_AND_MEAN" cppCompiler.define "NOMINMAX" cppCompiler.define "WIN64" cppCompiler.define "_WINDOWS" cppCompiler.define "UNICODE" cppCompiler.define "_UNICODE" cppCompiler.define "NDEBUG" cppCompiler.args "/nologo", "/MT", "/WX-", "/Wall", "/O2", "/Oi", "/Ot", "/GL", "/GS", "/Gy", "/fp:precise", "/std:c++17", "-wd4514", // Unreferenced inline function removed "-wd4548", // Expression before comma has no effect "-wd4625", // Copy constructor was implicitly defined as deleted "-wd4626", // Assignment operator was implicitly defined as deleted "-wd4710", // function not inlined "-wd4711", // function inlined "-wd4820", // Extra padding added to struct "-wd4946", // reinterpret_cast used between related classes: "-wd4996", // Thread safety for strerror "-wd5027", // Move assignment operator was implicitly defined as deleted "-I$jniSourceDir/main/include", "-I$jniSourceDir/unbundled/include", "-I$boringsslIncludeDir", "-I$jdkIncludeDir", "-I$jdkIncludeDir/win32" // Static link to BoringSSL linker.args "-WX", "ws2_32.lib", "advapi32.lib", "${libPath}\\ssl\\ssl.lib", "${libPath}\\crypto\\crypto.lib" } } // Never build a static library. withType(StaticLibraryBinarySpec) { buildable = false } } } } tasks { t -> $.binaries.withType(SharedLibraryBinarySpec).each { binary -> def nativeBuild = NativeBuildInfo.find(binary.targetPlatform) def classifier = nativeBuild.mavenClassifier() def source = binary.sharedLibraryFile // Copies the native library to a resource location that will be included in the jar. def copyTask = project.tasks.register("copyNativeLib${classifier}", Copy) { dependsOn binary.tasks.link from source // Rename the artifact to include the generated classifier rename '(.+)(\\.[^\\.]+)', "\$1-$classifier\$2" // Everything under will be included in the native jar. into nativeBuild.jarNativeResourcesDir() } processResources { dependsOn copyTask } processTestResources { dependsOn copyTask } // Now define a task to strip the release binary (linux only) if (osdetector.os == 'linux' && (!rootProject.hasProperty('nostrip') || !rootProject.nostrip.toBoolean())) { def stripTask = binary.tasks.taskName("strip") project.tasks.register(stripTask as String, Exec) { dependsOn binary.tasks.link executable "strip" args binary.tasks.link.linkedFile.asFile.get() } copyTask.configure { dependsOn stripTask } } } } } boolean isExecutableOnPath(executable) { FilenameFilter filter = new FilenameFilter() { @Override boolean accept(File dir, String name) { return executable == name } } for(String folder : System.getenv('PATH').split("" + File.pathSeparatorChar)) { File[] files = file(folder).listFiles(filter) if (files != null && files.size() > 0) { return true } } return false }