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