• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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