1import org.codehaus.groovy.runtime.InvokerHelper
2
3description = 'Conscrypt: OpenJdk'
4
5ext {
6    jniSourceDir = "$rootDir/common/src/jni"
7    assert file("$jniSourceDir").exists()
8
9    // Build the list of classifiers that will be used in the build.
10    arch32Name = 'x86'
11    arch64Name = 'x86_64'
12    nativeClassifiers = []
13    nativeClassifier64Bit = null
14    nativeClassifier32Bit = null
15    preferredClassifier = null
16    preferredSourceSet = null
17    preferredNativeFileDir = null
18    if (build64Bit) {
19        // Add the 64-Bit classifier first, as the preferred classifier.
20        nativeClassifier64Bit = classifierFor(osName, arch64Name)
21        nativeClassifiers += nativeClassifier64Bit
22        preferredClassifier = nativeClassifier64Bit
23        preferredSourceSet = sourceSetName(preferredClassifier)
24        preferredNativeFileDir = nativeResourcesDir(preferredClassifier)
25    }
26    if (build32Bit) {
27        nativeClassifier32Bit = classifierFor(osName, arch32Name)
28        nativeClassifiers += nativeClassifier32Bit
29        if (preferredClassifier == null) {
30            preferredClassifier = nativeClassifier32Bit
31            preferredSourceSet = sourceSetName(preferredClassifier)
32            preferredNativeFileDir = nativeResourcesDir(preferredClassifier)
33        }
34    }
35}
36
37sourceSets {
38
39    main {
40        java {
41            srcDirs += "${rootDir}/common/src/main/java"
42            srcDirs += project(':conscrypt-constants').sourceSets.main.java.srcDirs
43        }
44        resources {
45            srcDirs += "build/generated/resources"
46        }
47    }
48
49    platform {
50        java {
51            srcDirs = [ "src/main/java" ]
52            includes = [ "org/conscrypt/Platform.java" ]
53        }
54    }
55
56    test {
57        java {
58            srcDirs += "${rootDir}/common/src/test/java"
59        }
60        resources {
61            // This shouldn't be needed but seems to help IntelliJ locate the native artifact.
62            srcDirs += preferredNativeFileDir
63        }
64    }
65
66    // Add the source sets for each of the native build
67    nativeClassifiers.each { nativeClassifier ->
68        def sourceSetName = sourceSetName(nativeClassifier)
69        def testSourceSetName = testSourceSetName(nativeClassifier)
70
71        // Main sources for the native build
72        "$sourceSetName" {
73            output.dir(nativeResourcesDir(nativeClassifier), builtBy: "copyNativeLib${sourceSetName}")
74        }
75
76        // Test sources for the native build
77        "$testSourceSetName" {
78            java {
79                // Include the test source.
80                srcDirs = test.java.srcDirs
81            }
82            resources {
83                srcDirs = ["src/test/resources"]
84            }
85            output.dir(nativeResourcesDir(nativeClassifier), builtBy: "copyNativeLib${sourceSetName}")
86        }
87    }
88}
89
90compileJava {
91    dependsOn generateProperties
92}
93
94task platformJar(type: Jar) {
95    from sourceSets.platform.output
96}
97
98if (isExecutableOnPath('cpplint')) {
99    task cpplint(type: Exec) {
100        executable = 'cpplint'
101
102        // TODO(nmittler): Is there a better way of getting the JNI sources?
103        def pattern = ['**/*.cc', '**/*.h']
104        def sourceFiles = fileTree(dir: jniSourceDir, includes: pattern).asPath.tokenize(':')
105        // Adding roots so that class #ifdefs don't require full path from the project root.
106        args = sourceFiles
107
108        // Capture stderr from the process
109        errorOutput = new ByteArrayOutputStream();
110
111        // Need to ignore exit value so that doLast will execute.
112        ignoreExitValue = true
113
114        doLast {
115            // Create the report file.
116            def reportDir = file("${buildDir}/cpplint")
117            reportDir.mkdirs();
118            def reportFile = new File(reportDir, "report.txt")
119            def reportStream = new FileOutputStream(reportFile)
120
121            try {
122                // Check for failure
123                if (execResult != null) {
124                    execResult.assertNormalExitValue()
125                }
126            } catch (Exception e) {
127                // The process failed - get the error report from the stderr.
128                String report = errorOutput.toString();
129
130                // Write the report to the console.
131                System.err.println(report)
132
133                // Also write the report file.
134                reportStream.write(report.bytes);
135
136                // Extension method cpplint.output() can be used to obtain the report
137                ext.output = {
138                    return report
139                }
140
141                // Rethrow the exception to terminate the build.
142                throw e;
143            } finally {
144                reportStream.close();
145            }
146        }
147    }
148    check.dependsOn cpplint
149}
150
151configurations {
152    publicApiDocs
153    platform
154}
155
156artifacts {
157    platform platformJar
158}
159
160jar.manifest {
161    attributes ('BoringSSL-Version' : boringSslVersion,
162                'Automatic-Module-Name' : 'org.conscrypt')
163}
164
165dependencies {
166    // This is used for the @Internal annotation processing in JavaDoc
167    publicApiDocs project(':conscrypt-api-doclet')
168
169    compileOnly project(':conscrypt-constants'),
170                configurations.publicApiDocs
171
172    testImplementation project(':conscrypt-constants'),
173            project(':conscrypt-testing'),
174            libraries.junit,
175            libraries.mockito
176
177    testRuntimeClasspath sourceSets["$preferredSourceSet"].output
178
179    // Configure the dependencies for the native tests.
180    nativeClassifiers.each { nativeClassifier ->
181        def testCompileConfigName = testSourceSet(nativeClassifier).compileConfigurationName
182        "${testCompileConfigName}" (
183                sourceSets.main.output, // Explicitly add the main classes
184                project(':conscrypt-constants'),
185                project(':conscrypt-testing'),
186                libraries.junit,
187                libraries.mockito
188        )
189    }
190
191    platformCompileOnly sourceSets.main.output
192}
193
194nativeClassifiers.each { nativeClassifier ->
195    // Create the JAR task and add it's output to the published archives for this project
196    addNativeJar(nativeClassifier)
197
198    // Create the test task and have it auto run whenever the test task runs.
199    addNativeTest(nativeClassifier)
200
201    // Build the classes as part of the standard build.
202    classes.dependsOn sourceSet(nativeClassifier).classesTaskName
203    testClasses.dependsOn testSourceSet(nativeClassifier).classesTaskName
204}
205
206// Adds a JAR task for the native library.
207def addNativeJar(nativeClassifier) {
208    // Create a JAR for this configuration and add it to the output archives.
209    SourceSet sourceSet = sourceSet(nativeClassifier)
210    def jarTaskName = sourceSet.jarTaskName
211    task "$jarTaskName"(type: Jar) {
212        // Depend on the regular classes task
213        dependsOn classes
214        manifest = jar.manifest
215        classifier = nativeClassifier
216
217        from sourceSet.output + sourceSets.main.output
218    }
219
220    def jarTask = tasks["$jarTaskName"]
221
222    // Add the jar task to the standard build.
223    jar.dependsOn jarTask
224
225    // Add it to the 'archives' configuration so that the artifact will be automatically built and
226    // installed/deployed.
227    artifacts.add('archives', jarTask)
228}
229
230// Optionally adds a test task for the given platform
231def addNativeTest(nativeClassifier) {
232    SourceSet testSourceSet = testSourceSet(nativeClassifier)
233
234    // Just use the same name as the source set for the task.
235    def testTaskName = "${testSourceSet.name}"
236    def javaExecutable
237    def javaArchFlag
238    if (testSourceSet.name.endsWith("${arch32Name}Test")) {
239        // 32-bit test
240        javaExecutable = javaExecutable32 != null ? javaExecutable32 : test.executable
241        javaArchFlag = '-d32'
242    } else {
243        // 64-bit test
244        javaExecutable = javaExecutable64 != null ? javaExecutable64 : test.executable
245        javaArchFlag = '-d64'
246    }
247
248    // Execute the java executable to see if it supports the architecture flag.
249    def javaError = new ByteArrayOutputStream()
250    exec {
251        System.out.println("Running tests with java executable: " + javaExecutable + ".")
252        executable javaExecutable
253        args = ["$javaArchFlag", '-version']
254        ignoreExitValue true
255        errorOutput = javaError
256    }
257
258    // Only add the test if the javaArchFlag is supported for the selected JVM
259    def archSupported = !javaError.toString().toLowerCase().contains('error')
260    if (archSupported) {
261        task "$testTaskName"(type: Test) {
262            mustRunAfter test
263            jvmArgs javaArchFlag
264            executable = javaExecutable
265            InvokerHelper.setProperties(testLogging, test.testLogging.properties)
266            systemProperties = test.systemProperties
267        }
268        check.dependsOn "$testTaskName"
269    }
270}
271
272// Exclude all test classes from the default test suite.
273// We will test each available native artifact separately (see nativeClassifiers).
274test.exclude("**")
275
276javadoc {
277    dependsOn(configurations.publicApiDocs)
278    options.doclet = "org.conscrypt.doclet.FilterDoclet"
279    options.docletpath = configurations.publicApiDocs.files as List
280}
281
282model {
283    platforms {
284        x86 {
285            architecture arch32Name
286        }
287        x86_64 {
288            architecture arch64Name
289        }
290    }
291
292    buildTypes {
293        release
294    }
295
296    components {
297        // Builds the JNI library.
298        conscrypt_openjdk_jni(NativeLibrarySpec) {
299            if (build32Bit) { targetPlatform arch32Name }
300            if (build64Bit) { targetPlatform arch64Name }
301
302            sources {
303                cpp {
304                    source {
305                        srcDirs "$jniSourceDir/main/cpp"
306                        include "**/*.cc"
307                    }
308                }
309            }
310
311            binaries {
312                // Build the JNI lib as a shared library.
313                withType (SharedLibraryBinarySpec) {
314                    cppCompiler.define "CONSCRYPT_OPENJDK"
315
316                    // Set up 32-bit vs 64-bit build
317                    def building64Bit = false
318                    def libPath
319                    if (targetPlatform.getArchitecture().getName() == "x86") {
320                        libPath = "$boringssl32BuildDir"
321                    } else if (targetPlatform.getArchitecture().getName() == "x86-64") {
322                        libPath = "$boringssl64BuildDir"
323                        building64Bit = true
324                    } else {
325                        throw new GradleException("Unknown architecture: " +
326                                targetPlatform.getArchitecture().name)
327                    }
328
329                    if (toolChain in Clang || toolChain in Gcc) {
330                        cppCompiler.args "-Wall",
331                                "-fPIC",
332                                "-O3",
333                                "-std=c++11",
334                                "-I$jniSourceDir/main/include",
335                                "-I$jniSourceDir/unbundled/include",
336                                "-I$boringsslIncludeDir",
337                                "-I$jdkIncludeDir",
338                                "-I$jdkIncludeDir/linux",
339                                "-I$jdkIncludeDir/darwin",
340                                "-I$jdkIncludeDir/win32"
341                        if (rootProject.hasProperty('checkErrorQueue')) {
342                            System.out.println("Compiling with error queue checking enabled")
343                            cppCompiler.define "CONSCRYPT_CHECK_ERROR_QUEUE"
344                        }
345
346                        // Static link to BoringSSL
347                        linker.args "-O3",
348                                "-fvisibility=hidden",
349                                "-lstdc++",
350                                "-lpthread",
351                                libPath + "/ssl/libssl.a",
352                                libPath + "/crypto/libcrypto.a"
353                    } else if (toolChain in VisualCpp) {
354                        cppCompiler.define "DLL_EXPORT"
355                        cppCompiler.define "WIN32_LEAN_AND_MEAN"
356                        cppCompiler.define "NOMINMAX"
357                        if (building64Bit) {
358                            cppCompiler.define "WIN64"
359                        }
360                        cppCompiler.define "_WINDOWS"
361                        cppCompiler.define "UNICODE"
362                        cppCompiler.define "_UNICODE"
363                        cppCompiler.define "NDEBUG"
364
365                        cppCompiler.args "/nologo",
366                                "/MT",
367                                "/WX-",
368                                "/Wall",
369                                "/O2",
370                                "/Oi",
371                                "/Ot",
372                                "/GL",
373                                "/GS",
374                                "/Gy",
375                                "/fp:precise",
376                                "-wd4514", // Unreferenced inline function removed
377                                "-wd4548", // Expression before comma has no effect
378                                "-wd4625", // Copy constructor was implicitly defined as deleted
379                                "-wd4626", // Assignment operator was implicitly defined as deleted
380                                "-wd4710", // function not inlined
381                                "-wd4711", // function inlined
382                                "-wd4820", // Extra padding added to struct
383                                "-wd4946", // reinterpret_cast used between related classes:
384                                "-wd4996", // Thread safety for strerror
385                                "-wd5027", // Move assignment operator was implicitly defined as deleted
386                                "-I$jniSourceDir/main/include",
387                                "-I$jniSourceDir/unbundled/include",
388                                "-I$boringsslIncludeDir",
389                                "-I$jdkIncludeDir",
390                                "-I$jdkIncludeDir/win32"
391
392                        // Static link to BoringSSL
393                        linker.args "-WX",
394                                "ws2_32.lib",
395                                "advapi32.lib",
396                                "${libPath}\\ssl\\ssl.lib",
397                                "${libPath}\\crypto\\crypto.lib"
398                    }
399                }
400
401                // Never build a static library.
402                withType(StaticLibraryBinarySpec) {
403                    buildable = false
404                }
405            }
406        }
407    }
408
409    tasks { t ->
410        $.binaries.withType(SharedLibraryBinarySpec).each { binary ->
411            // Build the native artifact classifier from the OS and architecture.
412            def archName = binary.targetPlatform.architecture.name.replaceAll('-', '_')
413            def classifier = classifierFor(osName, archName)
414            def sourceSetName = sourceSetName("$classifier")
415            def source = binary.sharedLibraryFile
416
417            // Copies the native library to a resource location that will be included in the jar.
418            def copyTaskName = "copyNativeLib${sourceSetName}"
419            task "$copyTaskName"(type: Copy, dependsOn: binary.tasks.link) {
420                from source
421                // Rename the artifact to include the generated classifier
422                rename '(.+)(\\.[^\\.]+)', "\$1-$classifier\$2"
423                // Everything under will be included in the native jar.
424                into nativeResourcesDir(classifier) + '/META-INF/native'
425            }
426
427            // Now define a task to strip the release binary (linux only)
428            if (osName == 'linux' && (!rootProject.hasProperty('nostrip') ||
429                    !rootProject.nostrip.toBoolean())) {
430                def stripTask = binary.tasks.taskName("strip")
431                    task "$stripTask"(type: Exec) {
432                        dependsOn binary.tasks.link
433                        executable "strip"
434                        args binary.tasks.link.linkedFile.asFile.get()
435                    }
436                project.tasks["$copyTaskName"].dependsOn stripTask
437            }
438        }
439    }
440}
441
442boolean isExecutableOnPath(executable) {
443    FilenameFilter filter = new FilenameFilter() {
444        @Override
445        boolean accept(File dir, String name) {
446            return executable.equals(name);
447        }
448    }
449    for(String folder : System.getenv('PATH').split("" + File.pathSeparatorChar)) {
450        File[] files = file(folder).listFiles(filter)
451        if (files != null && files.size() > 0) {
452            return true;
453        }
454    }
455    return false;
456}
457
458String nativeResourcesDir(nativeClassifier) {
459    def sourceSetName = sourceSetName(nativeClassifier)
460    "${buildDir}/${sourceSetName}/native-resources"
461}
462
463SourceSet sourceSet(classifier) {
464    sourceSets[sourceSetName(classifier)]
465}
466
467SourceSet testSourceSet(classifier) {
468    sourceSets[testSourceSetName(classifier)]
469}
470
471static String classifierFor(osName, archName) {
472    "${osName}-${archName}"
473}
474
475static String sourceSetName(classifier) {
476    classifier.replaceAll("-", "_")
477}
478
479static String testSourceSetName(classifier) {
480    "${sourceSetName(classifier)}Test"
481}
482
483