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