1 /*
<lambda>null2  * 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 com.android.utils.appendCapitalized
20 import org.gradle.api.Action
21 import org.gradle.api.NamedDomainObjectFactory
22 import org.gradle.api.Project
23 import org.gradle.api.file.ConfigurableFileCollection
24 import org.gradle.api.file.RegularFile
25 import org.gradle.api.provider.ListProperty
26 import org.gradle.api.provider.Provider
27 import org.gradle.api.tasks.TaskProvider
28 import org.gradle.kotlin.dsl.listProperty
29 import org.jetbrains.kotlin.konan.target.HostManager
30 import org.jetbrains.kotlin.konan.target.KonanTarget
31 
32 /**
33  * A native compilation setup (C code) that can target multiple platforms.
34  *
35  * New targets can be added via the [configureTarget] method. Each configured target will have tasks
36  * to produce machine code (.o), shared library (.so / .dylib) or archive (.a).
37  *
38  * Common configuration between targets can be done via the [configureEachTarget] method.
39  *
40  * @see NativeTargetCompilation for configuration details for each target.
41  */
42 class MultiTargetNativeCompilation(
43     internal val project: Project,
44     internal val archiveName: String,
45 ) {
46     private val hostManager = HostManager()
47 
48     private val nativeTargets =
49         project.objects.domainObjectContainer(
50             NativeTargetCompilation::class.java,
51             Factory(project, archiveName)
52         )
53 
54     /** Returns true if native code targeting [konanTarget] can be compiled on this host machine. */
55     fun canCompileOnCurrentHost(konanTarget: KonanTarget) = hostManager.isEnabled(konanTarget)
56 
57     /** Calls the given [action] for each added [KonanTarget] in this compilation. */
58     @Suppress("unused") // used in build.gradle
59     fun configureEachTarget(action: Action<NativeTargetCompilation>) {
60         nativeTargets.configureEach(action)
61     }
62 
63     /**
64      * Returns a [RegularFile] provider that points to the shared library output for the given
65      * [konanTarget].
66      */
67     fun sharedObjectOutputFor(konanTarget: KonanTarget): Provider<RegularFile> {
68         return nativeTargets.named(konanTarget.name).flatMap { nativeTargetCompilation ->
69             nativeTargetCompilation.sharedLibTask.flatMap { it.clangParameters.outputFile }
70         }
71     }
72 
73     fun sharedArchiveOutputFor(konanTarget: KonanTarget): Provider<RegularFile> {
74         return nativeTargets.named(konanTarget.name).flatMap { nativeTargetCompilation ->
75             nativeTargetCompilation.archiveTask.flatMap { it.llvmArchiveParameters.outputFile }
76         }
77     }
78 
79     /**
80      * Adds the given [konanTarget] to the list of compilation target if it can be built on this
81      * machine. The [action] block can be used to further configure the parameters of that
82      * compilation.
83      */
84     @Suppress("MemberVisibilityCanBePrivate") // used in build.gradle
85     @JvmOverloads
86     fun configureTarget(konanTarget: KonanTarget, action: Action<NativeTargetCompilation>? = null) {
87         if (!canCompileOnCurrentHost(konanTarget)) {
88             // Cannot compile it on this host. This is similar to calling `ios` block in the build
89             // gradle file on a linux machine.
90             return
91         }
92         val nativeTarget =
93             if (nativeTargets.names.contains(konanTarget.name)) {
94                 nativeTargets.named(konanTarget.name)
95             } else {
96                 nativeTargets.register(konanTarget.name).also {
97                     // force evaluation of target so that tasks are registered b/325518502
98                     nativeTargets.getByName(konanTarget.name)
99                 }
100             }
101         if (action != null) {
102             nativeTarget.configure(action)
103         }
104     }
105 
106     /**
107      * Returns a provider for the given konan target and throws an exception if it is not
108      * registered.
109      */
110     fun targetProvider(konanTarget: KonanTarget): Provider<NativeTargetCompilation> =
111         nativeTargets.named(konanTarget.name)
112 
113     /**
114      * Returns a provider that contains the list of [NativeTargetCompilation]s that matches the
115      * given [predicate].
116      *
117      * You can use this provider to obtain the compilation for targets needed without forcing the
118      * creation of all other targets.
119      */
120     internal fun targetsProvider(
121         predicate: (KonanTarget) -> Boolean
122     ): Provider<List<NativeTargetCompilation>> =
123         project.provider {
124             nativeTargets.names
125                 .filter { predicate(SerializableKonanTarget(it).asKonanTarget) }
126                 .map { nativeTargets.getByName(it) }
127         }
128 
129     /** Returns true if the given [konanTarget] is configured as a compilation target. */
130     fun hasTarget(konanTarget: KonanTarget) = nativeTargets.names.contains(konanTarget.name)
131 
132     /**
133      * Convenience method to configure multiple targets at the same time. This is equal to calling
134      * [configureTarget] for each given [konanTargets].
135      */
136     @Suppress("unused") // used in build.gradle
137     @JvmOverloads
138     fun configureTargets(
139         konanTargets: List<KonanTarget>,
140         action: Action<NativeTargetCompilation>? = null
141     ) = konanTargets.map { configureTarget(it, action) }
142 
143     /**
144      * Internal factory for creating instances of [nativeTargets]. This factory sets up all
145      * necessary inputs and their tasks for the native target.
146      */
147     private class Factory(
148         private val project: Project,
149         private val archiveName: String,
150     ) : NamedDomainObjectFactory<NativeTargetCompilation> {
151         /** Shared task prefix for this archive */
152         private val taskPrefix = "nativeCompilationFor".appendCapitalized(archiveName)
153 
154         /** Shared output directory prefix for tasks of this archive. */
155         private val outputDir =
156             project.layout.buildDirectory.dir("clang".appendCapitalized(archiveName))
157 
158         override fun create(name: String): NativeTargetCompilation {
159             return create(SerializableKonanTarget(name))
160         }
161 
162         @JvmName("createWithSerializableKonanTarget")
163         private fun create(
164             serializableKonanTarget: SerializableKonanTarget
165         ): NativeTargetCompilation {
166             val includes = project.objects.fileCollection()
167             val sources = project.objects.fileCollection()
168             val freeArgs = project.objects.listProperty<String>()
169             val linkedObjects = project.objects.fileCollection()
170             val linkerArgs = project.objects.listProperty<String>()
171             val compileTask =
172                 createCompileTask(serializableKonanTarget, includes, sources, freeArgs)
173             val archiveTask = createArchiveTask(serializableKonanTarget, compileTask)
174             val sharedLibTask =
175                 createSharedLibraryTask(
176                     serializableKonanTarget,
177                     compileTask,
178                     linkedObjects,
179                     linkerArgs
180                 )
181             return NativeTargetCompilation(
182                 project = project,
183                 konanTarget = serializableKonanTarget.asKonanTarget,
184                 compileTask = compileTask,
185                 archiveTask = archiveTask,
186                 sharedLibTask = sharedLibTask,
187                 sources = sources,
188                 includes = includes,
189                 linkedObjects = linkedObjects,
190                 linkerArgs = linkerArgs,
191                 freeArgs = freeArgs
192             )
193         }
194 
195         private fun createArchiveTask(
196             serializableKonanTarget: SerializableKonanTarget,
197             compileTask: TaskProvider<ClangCompileTask>
198         ): TaskProvider<ClangArchiveTask> {
199             val archiveTaskName =
200                 taskPrefix.appendCapitalized("archive", serializableKonanTarget.name)
201             val archiveTask =
202                 project.tasks.register(archiveTaskName, ClangArchiveTask::class.java) { task ->
203                     val konanTarget = serializableKonanTarget.asKonanTarget
204                     val archiveFileName =
205                         listOf(
206                                 konanTarget.family.staticPrefix,
207                                 archiveName,
208                                 ".",
209                                 konanTarget.family.staticSuffix
210                             )
211                             .joinToString("")
212                     task.usesService(KonanBuildService.obtain(project))
213                     task.llvmArchiveParameters.let { llvmAr ->
214                         llvmAr.outputFile.set(
215                             outputDir.map { it.file("$serializableKonanTarget/$archiveFileName") }
216                         )
217                         llvmAr.konanTarget.set(serializableKonanTarget)
218                         llvmAr.objectFiles.from(compileTask.map { it.clangParameters.output })
219                     }
220                 }
221             return archiveTask
222         }
223 
224         private fun createCompileTask(
225             serializableKonanTarget: SerializableKonanTarget,
226             includes: ConfigurableFileCollection?,
227             sources: ConfigurableFileCollection?,
228             freeArgs: ListProperty<String>
229         ): TaskProvider<ClangCompileTask> {
230             val compileTaskName =
231                 taskPrefix.appendCapitalized("compile", serializableKonanTarget.name)
232             val compileTask =
233                 project.tasks.register(compileTaskName, ClangCompileTask::class.java) { compileTask
234                     ->
235                     compileTask.usesService(KonanBuildService.obtain(project))
236                     compileTask.clangParameters.let { clang ->
237                         clang.output.set(
238                             outputDir.map { it.dir("compile/$serializableKonanTarget") }
239                         )
240                         clang.includes.from(includes)
241                         clang.sources.from(sources)
242                         clang.freeArgs.addAll(freeArgs)
243                         clang.konanTarget.set(serializableKonanTarget)
244                     }
245                 }
246             return compileTask
247         }
248 
249         private fun createSharedLibraryTask(
250             serializableKonanTarget: SerializableKonanTarget,
251             compileTask: TaskProvider<ClangCompileTask>,
252             linkedObjects: ConfigurableFileCollection,
253             linkerArgs: ListProperty<String>
254         ): TaskProvider<ClangSharedLibraryTask> {
255             val archiveTaskName =
256                 taskPrefix.appendCapitalized("createSharedLibrary", serializableKonanTarget.name)
257             val archiveTask =
258                 project.tasks.register(archiveTaskName, ClangSharedLibraryTask::class.java) { task
259                     ->
260                     val konanTarget = serializableKonanTarget.asKonanTarget
261                     val archiveFileName =
262                         listOf(
263                                 konanTarget.family.dynamicPrefix,
264                                 archiveName,
265                                 ".",
266                                 konanTarget.family.dynamicSuffix
267                             )
268                             .joinToString("")
269 
270                     task.usesService(KonanBuildService.obtain(project))
271                     task.clangParameters.let { clang ->
272                         clang.outputFile.set(
273                             outputDir.map { it.file("$serializableKonanTarget/$archiveFileName") }
274                         )
275                         clang.konanTarget.set(serializableKonanTarget)
276                         clang.objectFiles.from(compileTask.map { it.clangParameters.output })
277                         clang.linkedObjects.from(linkedObjects)
278                         clang.linkerArgs.addAll(linkerArgs)
279                     }
280                 }
281             return archiveTask
282         }
283     }
284 }
285