1 /*
2  * Copyright 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.build.clang
18 
19 import androidx.build.ProjectLayoutType
20 import java.io.File
21 import org.gradle.api.Named
22 import org.gradle.api.Project
23 import org.gradle.api.file.ConfigurableFileCollection
24 import org.gradle.api.provider.ListProperty
25 import org.gradle.api.tasks.TaskProvider
26 import org.jetbrains.kotlin.konan.target.Family
27 import org.jetbrains.kotlin.konan.target.KonanTarget
28 
29 /**
30  * Represents a C compilation for a single [konanTarget].
31  *
32  * @param konanTarget Target host for the compilation.
33  * @param compileTask The task that compiles the sources and build .o file for each source file.
34  * @param archiveTask The task that will archive the output of the [compileTask] into a single .a
35  *   file.
36  * @param sharedLibTask The task that will created a shared library from the output of [compileTask]
37  *   that also optionally links with [linkedObjects]
38  * @param sources List of source files for the compilation.
39  * @param includes List of include directories containing .h files for the compilation.
40  * @param linkedObjects List of object files that should be dynamically linked in the final shared
41  *   object output.
42  * @param linkerArgs Arguments that will be passed into linker when creating a shared library.
43  * @param freeArgs Arguments that will be passed into clang for compilation.
44  */
45 class NativeTargetCompilation
46 internal constructor(
47     val project: Project,
48     val konanTarget: KonanTarget,
49     internal val compileTask: TaskProvider<ClangCompileTask>,
50     internal val archiveTask: TaskProvider<ClangArchiveTask>,
51     internal val sharedLibTask: TaskProvider<ClangSharedLibraryTask>,
52     val sources: ConfigurableFileCollection,
53     val includes: ConfigurableFileCollection,
54     val linkedObjects: ConfigurableFileCollection,
55     @Suppress("unused") // used via build.gradle
56     val linkerArgs: ListProperty<String>,
57     @Suppress("unused") // used via build.gradle
58     val freeArgs: ListProperty<String>
59 ) : Named {
getNamenull60     override fun getName(): String = konanTarget.name
61 
62     /**
63      * Dynamically links the shared library output of this target with the given [dependency]'s
64      * object library output.
65      */
66     @Suppress("unused") // used from build.gradle
67     fun linkWith(dependency: MultiTargetNativeCompilation) {
68         linkedObjects.from(dependency.sharedObjectOutputFor(konanTarget))
69     }
70 
71     /**
72      * Statically include the shared library output of this target with the given [dependency]'s
73      * archive library output.
74      */
75     @Suppress("unused") // used from build.gradle
includenull76     fun include(dependency: MultiTargetNativeCompilation) {
77         linkedObjects.from(dependency.sharedArchiveOutputFor(konanTarget))
78     }
79 
80     /** Convenience method to add jni headers to the compilation. */
81     @Suppress("unused") // used from build.gradle
addJniHeadersnull82     fun addJniHeaders() {
83         if (konanTarget.family == Family.ANDROID) {
84             // android already has JNI
85             return
86         }
87 
88         includes.from(project.provider { findJniHeaderDirectories() })
89     }
90 
findJniHeaderDirectoriesnull91     private fun findJniHeaderDirectories(): List<File> {
92         // TODO b/306669673 add support for GitHub builds.
93         // we need to find 2 jni header files
94         // jni.h -> This is the same across all platforms
95         // jni_md.h -> Includes machine dependant definitions.
96         // Internal Devs: You can read more about it here:  http://go/androidx-jni-cross-compilation
97         val javaHome = File(System.getProperty("java.home"))
98         if (ProjectLayoutType.isPlayground(project)) {
99             return findJniHeadersInPlayground(javaHome)
100         }
101         // for jni_md, we need to find the prebuilts because each jdk ships with jni_md only for
102         // its own target family.
103         val jdkPrebuiltsRoot = javaHome.parentFile
104 
105         val relativeHeaderPaths =
106             when (konanTarget.family) {
107                 Family.MINGW -> {
108                     listOf("windows-x86/include", "windows-x86/include/win32")
109                 }
110                 Family.OSX -> {
111                     // it is OK that we are using x86 here, they are the same files (openjdk only
112                     // distinguishes between unix and windows).
113                     listOf("darwin-x86/include", "darwin-x86/include/darwin")
114                 }
115                 Family.LINUX -> {
116                     listOf(
117                         "linux-x86/include",
118                         "linux-x86/include/linux",
119                     )
120                 }
121                 else -> error("unsupported family ($konanTarget) for JNI compilation")
122             }
123         return relativeHeaderPaths
124             .map { jdkPrebuiltsRoot.resolve(it) }
125             .onEach {
126                 check(it.exists()) {
127                     "Cannot find header directory (${it.name}) in ${it.canonicalPath}"
128                 }
129             }
130     }
131 
132     /**
133      * JDK ships with JNI headers only for the current platform. As a result, we don't have access
134      * to cross-platform jni headers. They are mostly the same and we don't ship cross compiled code
135      * from GitHub so it is acceptable to use local JNI headers for cross platform compilation on
136      * GitHub.
137      */
findJniHeadersInPlaygroundnull138     private fun findJniHeadersInPlayground(javaHome: File): List<File> {
139         val include = File(javaHome, "include")
140         if (!include.exists()) {
141             error("Cannot find header directory in $javaHome")
142         }
143         return listOf(
144                 include,
145                 File(include, "darwin"),
146                 File(include, "linux"),
147                 File(include, "win32"),
148             )
149             .filter { it.exists() }
150     }
151 }
152