1 /*
<lambda>null2 * Copyright 2022 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 @file:Suppress("UnstableApiUsage") // b/393137152
18
19 package androidx.build
20
21 import androidx.build.clang.AndroidXClang
22 import androidx.build.clang.MultiTargetNativeCompilation
23 import androidx.build.clang.NativeLibraryBundler
24 import androidx.build.clang.configureCinterop
25 import com.android.build.api.dsl.KotlinMultiplatformAndroidLibraryTarget
26 import com.android.build.gradle.api.KotlinMultiplatformAndroidPlugin
27 import groovy.lang.Closure
28 import java.io.File
29 import javax.inject.Inject
30 import org.gradle.api.Action
31 import org.gradle.api.GradleException
32 import org.gradle.api.NamedDomainObjectCollection
33 import org.gradle.api.Project
34 import org.gradle.api.artifacts.Configuration
35 import org.gradle.api.configuration.BuildFeatures
36 import org.gradle.api.plugins.ExtensionAware
37 import org.gradle.kotlin.dsl.findByType
38 import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
39 import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
40 import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
41 import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
42 import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper
43 import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
44 import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetTree
45 import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
46 import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
47 import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeCompilation
48 import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
49 import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTargetWithHostTests
50 import org.jetbrains.kotlin.gradle.targets.js.binaryen.BinaryenRootExtension
51 import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsTargetDsl
52 import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinWasmTargetDsl
53 import org.jetbrains.kotlin.gradle.targets.js.ir.DefaultIncrementalSyncTask
54 import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsEnvSpec
55 import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest
56 import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockMismatchReport
57 import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnRootExtension
58 import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget
59 import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
60
61 /**
62 * [AndroidXMultiplatformExtension] is an extension that wraps specific functionality of the Kotlin
63 * multiplatform extension, and applies the Kotlin multiplatform plugin when it is used. The purpose
64 * of wrapping is to prevent targets from being added when the platform has not been enabled. e.g.
65 * the `macosX64` target is gated on a `project.enableMac` check.
66 */
67 abstract class AndroidXMultiplatformExtension(val project: Project) {
68
69 @get:Inject abstract val buildFeatures: BuildFeatures
70
71 var enableBinaryCompatibilityValidator = true
72
73 // Kotlin multiplatform plugin is only applied if at least one target / sourceset is added.
74 private val kotlinExtensionDelegate = lazy {
75 project.validateMultiplatformPluginHasNotBeenApplied()
76 project.plugins.apply(KotlinMultiplatformPluginWrapper::class.java)
77 project.multiplatformExtension!!
78 }
79 private val kotlinExtension: KotlinMultiplatformExtension by kotlinExtensionDelegate
80 private val agpKmpExtensionDelegate = lazy {
81 // make sure to initialize the kotlin extension by accessing the property
82 val extension = (kotlinExtension as ExtensionAware)
83 project.plugins.apply(KotlinMultiplatformAndroidPlugin::class.java)
84 extension.extensions.getByType(KotlinMultiplatformAndroidLibraryTarget::class.java)
85 }
86
87 val agpKmpExtension: KotlinMultiplatformAndroidLibraryTarget by agpKmpExtensionDelegate
88
89 /**
90 * The list of platforms that have been declared as supported in the build configuration.
91 *
92 * This may be a superset of the currently enabled platforms in [targetPlatforms].
93 */
94 val supportedPlatforms: MutableSet<PlatformIdentifier> = mutableSetOf()
95
96 /**
97 * The list of platforms that are currently enabled.
98 *
99 * This will vary across build environments. For example, a project's build configuration may
100 * have requested `mac()` but this is not available when building on Linux.
101 */
102 val targetPlatforms: List<String>
103 get() =
104 if (kotlinExtensionDelegate.isInitialized()) {
105 kotlinExtension.targets.mapNotNull {
106 if (it.targetName != "metadata") {
107 it.targetName
108 } else {
109 null
110 }
111 }
112 } else {
113 throw GradleException("Kotlin multi-platform extension has not been initialized")
114 }
115
116 /**
117 * Default platform identifier used for specifying POM dependencies.
118 *
119 * This platform will be added as a dependency to the multi-platform anchor artifact's POM
120 * publication. For example, if the anchor artifact is `collection` and the default platform is
121 * `jvm`, then the POM for `collection` will express a dependency on `collection-jvm`. This
122 * ensures that developers who are silently upgrade to KMP artifacts but are not using Gradle
123 * still see working artifacts.
124 *
125 * If no default was specified and a single platform is requested (ex. using [jvm]), returns the
126 * identifier for that platform.
127 */
128 var defaultPlatform: String? = null
129 get() = field ?: supportedPlatforms.singleOrNull()?.id
130 set(value) {
131 if (value != null) {
132 if (supportedPlatforms.none { it.id == value }) {
133 throw GradleException(
134 "Platform $value has not been requested as a target. " +
135 "Available platforms are: " +
136 supportedPlatforms.joinToString(", ") { it.id }
137 )
138 }
139 if (targetPlatforms.none { it == value }) {
140 throw GradleException(
141 "Platform $value is not available in this build " +
142 "environment. Available platforms are: " +
143 targetPlatforms.joinToString(", ")
144 )
145 }
146 }
147 field = value
148 }
149
150 val targets: NamedDomainObjectCollection<KotlinTarget>
151 get() = kotlinExtension.targets
152
153 /** Helper class to access Clang functionality. */
154 private val clang = AndroidXClang(project)
155
156 /** Helper class to bundle outputs of clang compilation into an AAR / JAR. */
157 private val nativeLibraryBundler = NativeLibraryBundler(project)
158
159 internal fun hasNativeTarget(): Boolean {
160 // it is important to check initialized here not to trigger initialization
161 return kotlinExtensionDelegate.isInitialized() &&
162 targets.any { it.platformType == KotlinPlatformType.native }
163 }
164
165 internal fun hasAndroidMultiplatform(): Boolean {
166 return agpKmpExtensionDelegate.isInitialized()
167 }
168
169 fun sourceSets(closure: Closure<*>) {
170 if (kotlinExtensionDelegate.isInitialized()) {
171 kotlinExtension.sourceSets.configure(closure).also {
172 kotlinExtension.sourceSets.configureEach { sourceSet ->
173 if (sourceSet.name == "main" || sourceSet.name == "test") {
174 throw Exception(
175 "KMP-enabled projects must use target-prefixed " +
176 "source sets, e.g. androidMain or commonTest, rather than main or test"
177 )
178 }
179 }
180 }
181 }
182 }
183
184 /**
185 * Creates a multi-target native compilation with the given [archiveName].
186 *
187 * The given [configure] action can be used to add targets, sources, includes etc.
188 *
189 * The outputs of this compilation is not added to any artifact by default.
190 * * To use the outputs via cinterop (kotlin native), use the [createCinterop] function.
191 * * To bundle the outputs inside a JAR (to be loaded at runtime), use the
192 * [addNativeLibrariesToResources] function.
193 * * To bundle the outputs inside an AAR (to be loaded at runtime), use the
194 * [addNativeLibrariesToJniLibs] function.
195 *
196 * @param archiveName The archive file name for the native artifacts (.so, .a or .o)
197 * @param configure Action block to configure the compilation.
198 */
199 fun createNativeCompilation(
200 archiveName: String,
201 configure: Action<MultiTargetNativeCompilation>
202 ): MultiTargetNativeCompilation {
203 return clang.createNativeCompilation(archiveName = archiveName, configure = configure)
204 }
205
206 /**
207 * Creates a Kotlin Native cinterop configuration for the given [nativeTarget] main compilation
208 * from the outputs of [nativeCompilation].
209 *
210 * @param nativeTarget The kotlin native target for which a new cinterop will be added on the
211 * main compilation.
212 * @param nativeCompilation The [MultiTargetNativeCompilation] which will be embedded into the
213 * generated cinterop klib.
214 * @param cinteropName The name of the cinterop definition. A matching "<cinteropName.def>" file
215 * needs to be present in the default cinterop location
216 * (src/nativeInterop/cinterop/<cinteropName.def>).
217 */
218 @JvmOverloads
219 fun createCinterop(
220 nativeTarget: KotlinNativeTarget,
221 nativeCompilation: MultiTargetNativeCompilation,
222 cinteropName: String = nativeCompilation.archiveName
223 ) {
224 createCinterop(
225 kotlinNativeCompilation =
226 nativeTarget.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME)
227 as KotlinNativeCompilation,
228 nativeCompilation = nativeCompilation,
229 cinteropName = cinteropName
230 )
231 }
232
233 /**
234 * Creates a Kotlin Native cinterop configuration for the given [kotlinNativeCompilation] from
235 * the outputs of [nativeCompilation].
236 *
237 * @param kotlinNativeCompilation The kotlin native compilation for which a new cinterop will be
238 * added
239 * @param nativeCompilation The [MultiTargetNativeCompilation] which will be embedded into the
240 * generated cinterop klib.
241 * @param cinteropName The name of the cinterop definition. A matching "<cinteropName.def>" file
242 * needs to be present in the default cinterop location
243 * (src/nativeInterop/cinterop/<cinteropName.def>).
244 */
245 @JvmOverloads
246 fun createCinterop(
247 kotlinNativeCompilation: KotlinNativeCompilation,
248 nativeCompilation: MultiTargetNativeCompilation,
249 cinteropName: String = nativeCompilation.archiveName
250 ) {
251 nativeCompilation.configureCinterop(
252 kotlinNativeCompilation = kotlinNativeCompilation,
253 cinteropName = cinteropName
254 )
255 }
256
257 /**
258 * Creates a Kotlin Native cinterop configuration for the given [kotlinNativeCompilation] from
259 * the single output of a configuration.
260 *
261 * @param kotlinNativeCompilation The kotlin native compilation for which a new cinterop will be
262 * added
263 * @param configuration The configuration to resolve. It is expected for the configuration to
264 * contain a single file of the archive file to be referenced in the C interop definition
265 * file.
266 */
267 fun createCinteropFromArchiveConfiguration(
268 kotlinNativeCompilation: KotlinNativeCompilation,
269 configuration: Configuration
270 ) {
271 configureCinterop(project, kotlinNativeCompilation, configuration)
272 }
273
274 /** @see NativeLibraryBundler.addNativeLibrariesToJniLibs */
275 @JvmOverloads
276 fun addNativeLibrariesToJniLibs(
277 androidTarget: KotlinAndroidTarget,
278 nativeCompilation: MultiTargetNativeCompilation,
279 forTest: Boolean = false
280 ) =
281 nativeLibraryBundler.addNativeLibrariesToJniLibs(
282 androidTarget = androidTarget,
283 nativeCompilation = nativeCompilation,
284 forTest = forTest
285 )
286
287 /**
288 * Convenience method to add bundle native libraries with a test jar.
289 *
290 * @see addNativeLibrariesToResources
291 */
292 fun addNativeLibrariesToTestResources(
293 jvmTarget: KotlinJvmTarget,
294 nativeCompilation: MultiTargetNativeCompilation
295 ) =
296 addNativeLibrariesToResources(
297 jvmTarget = jvmTarget,
298 nativeCompilation = nativeCompilation,
299 compilationName = KotlinCompilation.TEST_COMPILATION_NAME
300 )
301
302 /** @see NativeLibraryBundler.addNativeLibrariesToResources */
303 @JvmOverloads
304 fun addNativeLibrariesToResources(
305 jvmTarget: KotlinJvmTarget,
306 nativeCompilation: MultiTargetNativeCompilation,
307 compilationName: String = KotlinCompilation.MAIN_COMPILATION_NAME
308 ) =
309 nativeLibraryBundler.addNativeLibrariesToResources(
310 jvmTarget = jvmTarget,
311 nativeCompilation = nativeCompilation,
312 compilationName = compilationName
313 )
314
315 /**
316 * Sets the default target platform.
317 *
318 * The default target platform *must* be enabled in all build environments. For projects which
319 * request multiple target platforms, this method *must* be called to explicitly specify a
320 * default target platform.
321 *
322 * See [defaultPlatform] for details on how the value is used.
323 */
324 fun defaultPlatform(value: PlatformIdentifier) {
325 defaultPlatform = value.id
326 }
327
328 @JvmOverloads
329 fun jvm(block: Action<KotlinJvmTarget>? = null): KotlinJvmTarget? {
330 supportedPlatforms.add(PlatformIdentifier.JVM)
331 return if (project.enableJvm()) {
332 kotlinExtension.jvm { block?.execute(this) }
333 } else {
334 null
335 }
336 }
337
338 @JvmOverloads
339 fun jvmStubs(
340 runTests: Boolean = false,
341 block: Action<KotlinJvmTarget>? = null
342 ): KotlinJvmTarget? {
343 supportedPlatforms.add(PlatformIdentifier.JVM_STUBS)
344 return if (project.enableJvm()) {
345 kotlinExtension.jvm("jvmStubs") {
346 block?.execute(this)
347 project.tasks.named("jvmStubsTest").configure {
348 // don't try running common tests for stubs target if disabled
349 it.enabled = runTests
350 }
351 }
352 } else {
353 null
354 }
355 }
356
357 @OptIn(ExperimentalKotlinGradlePluginApi::class)
358 @JvmOverloads
359 fun androidTarget(block: Action<KotlinAndroidTarget>? = null): KotlinAndroidTarget? {
360 supportedPlatforms.add(PlatformIdentifier.ANDROID)
361 return if (project.enableJvm()) {
362 kotlinExtension.androidTarget {
363 // we need to allow instrumented test to depend on commonTest/jvmTest, which is not
364 // default.
365 // see https://youtrack.jetbrains.com/issue/KT-62594
366 instrumentedTestVariant.sourceSetTree.set(KotlinSourceSetTree.test)
367 block?.execute(this)
368 }
369 } else {
370 null
371 }
372 }
373
374 @JvmOverloads
375 fun androidNative(block: Action<KotlinNativeTarget>? = null): List<KotlinNativeTarget> {
376 return listOfNotNull(
377 androidNativeX86(block),
378 androidNativeX64(block),
379 androidNativeArm64(block),
380 androidNativeArm32(block)
381 )
382 }
383
384 @JvmOverloads
385 fun androidNativeX86(block: Action<KotlinNativeTarget>? = null): KotlinNativeTarget? {
386 supportedPlatforms.add(PlatformIdentifier.ANDROID_NATIVE_X86)
387 return if (project.enableAndroidNative()) {
388 kotlinExtension.androidNativeX86 { block?.execute(this) }
389 } else {
390 null
391 }
392 }
393
394 @JvmOverloads
395 fun androidNativeX64(block: Action<KotlinNativeTarget>? = null): KotlinNativeTarget? {
396 supportedPlatforms.add(PlatformIdentifier.ANDROID_NATIVE_X64)
397 return if (project.enableAndroidNative()) {
398 kotlinExtension.androidNativeX64 { block?.execute(this) }
399 } else {
400 null
401 }
402 }
403
404 @JvmOverloads
405 fun androidNativeArm64(block: Action<KotlinNativeTarget>? = null): KotlinNativeTarget? {
406 supportedPlatforms.add(PlatformIdentifier.ANDROID_NATIVE_ARM64)
407 return if (project.enableAndroidNative()) {
408 kotlinExtension.androidNativeArm64 { block?.execute(this) }
409 } else {
410 null
411 }
412 }
413
414 @JvmOverloads
415 fun androidNativeArm32(block: Action<KotlinNativeTarget>? = null): KotlinNativeTarget? {
416 supportedPlatforms.add(PlatformIdentifier.ANDROID_NATIVE_ARM32)
417 return if (project.enableAndroidNative()) {
418 kotlinExtension.androidNativeArm32 { block?.execute(this) }
419 } else {
420 null
421 }
422 }
423
424 @JvmOverloads
425 fun androidLibrary(
426 block: Action<KotlinMultiplatformAndroidLibraryTarget>? = null
427 ): KotlinMultiplatformAndroidLibraryTarget? {
428 supportedPlatforms.add(PlatformIdentifier.ANDROID)
429 return if (project.enableJvm()) {
430 agpKmpExtension.also { block?.execute(it) }
431 } else {
432 null
433 }
434 }
435
436 @JvmOverloads
437 fun desktop(block: Action<KotlinJvmTarget>? = null): KotlinJvmTarget? {
438 supportedPlatforms.add(PlatformIdentifier.DESKTOP)
439 return if (project.enableDesktop()) {
440 kotlinExtension.jvm("desktop") { block?.execute(this) }
441 } else {
442 null
443 }
444 }
445
446 @JvmOverloads
447 fun mingwX64(block: Action<KotlinNativeTarget>? = null): KotlinNativeTargetWithHostTests? {
448 supportedPlatforms.add(PlatformIdentifier.MINGW_X_64)
449 return if (project.enableWindows()) {
450 kotlinExtension.mingwX64 { block?.execute(this) }
451 } else {
452 null
453 }
454 }
455
456 /** Configures all mac targets supported by AndroidX. */
457 @JvmOverloads
458 fun mac(block: Action<KotlinNativeTarget>? = null): List<KotlinNativeTarget> {
459 return listOfNotNull(macosX64(block), macosArm64(block))
460 }
461
462 @JvmOverloads
463 fun macosX64(block: Action<KotlinNativeTarget>? = null): KotlinNativeTargetWithHostTests? {
464 supportedPlatforms.add(PlatformIdentifier.MAC_OSX_64)
465 return if (project.enableMac()) {
466 kotlinExtension.macosX64 { block?.execute(this) }
467 } else {
468 null
469 }
470 }
471
472 @JvmOverloads
473 fun macosArm64(block: Action<KotlinNativeTarget>? = null): KotlinNativeTargetWithHostTests? {
474 supportedPlatforms.add(PlatformIdentifier.MAC_ARM_64)
475 return if (project.enableMac()) {
476 kotlinExtension.macosArm64 { block?.execute(this) }
477 } else {
478 null
479 }
480 }
481
482 /** Configures all ios targets supported by AndroidX. */
483 @JvmOverloads
484 fun ios(block: Action<KotlinNativeTarget>? = null): List<KotlinNativeTarget> {
485 return listOfNotNull(iosX64(block), iosArm64(block), iosSimulatorArm64(block))
486 }
487
488 @JvmOverloads
489 fun iosArm64(block: Action<KotlinNativeTarget>? = null): KotlinNativeTarget? {
490 supportedPlatforms.add(PlatformIdentifier.IOS_ARM_64)
491 return if (project.enableMac()) {
492 kotlinExtension.iosArm64 { block?.execute(this) }
493 } else {
494 null
495 }
496 }
497
498 @JvmOverloads
499 fun iosX64(block: Action<KotlinNativeTarget>? = null): KotlinNativeTarget? {
500 supportedPlatforms.add(PlatformIdentifier.IOS_X_64)
501 return if (project.enableMac()) {
502 kotlinExtension.iosX64 { block?.execute(this) }
503 } else {
504 null
505 }
506 }
507
508 @JvmOverloads
509 fun iosSimulatorArm64(block: Action<KotlinNativeTarget>? = null): KotlinNativeTarget? {
510 supportedPlatforms.add(PlatformIdentifier.IOS_SIMULATOR_ARM_64)
511 return if (project.enableMac()) {
512 kotlinExtension.iosSimulatorArm64 { block?.execute(this) }
513 } else {
514 null
515 }
516 }
517
518 /** Configures all watchos targets supported by AndroidX. */
519 @JvmOverloads
520 fun watchos(block: Action<KotlinNativeTarget>? = null): List<KotlinNativeTarget> {
521 return listOfNotNull(
522 watchosX64(block),
523 watchosArm32(block),
524 watchosArm64(block),
525 watchosDeviceArm64(block),
526 watchosSimulatorArm64(block)
527 )
528 }
529
530 @JvmOverloads
531 fun watchosArm32(block: Action<KotlinNativeTarget>? = null): KotlinNativeTarget? {
532 supportedPlatforms.add(PlatformIdentifier.WATCHOS_ARM_32)
533 return if (project.enableMac()) {
534 kotlinExtension.watchosArm32 { block?.execute(this) }
535 } else {
536 null
537 }
538 }
539
540 @JvmOverloads
541 fun watchosArm64(block: Action<KotlinNativeTarget>? = null): KotlinNativeTarget? {
542 supportedPlatforms.add(PlatformIdentifier.WATCHOS_ARM_64)
543 return if (project.enableMac()) {
544 kotlinExtension.watchosArm64 { block?.execute(this) }
545 } else {
546 null
547 }
548 }
549
550 @JvmOverloads
551 fun watchosDeviceArm64(block: Action<KotlinNativeTarget>? = null): KotlinNativeTarget? {
552 supportedPlatforms.add(PlatformIdentifier.WATCHOS_DEVICE_ARM_64)
553 return if (project.enableMac()) {
554 kotlinExtension.watchosDeviceArm64 { block?.execute(this) }
555 } else {
556 null
557 }
558 }
559
560 @JvmOverloads
561 fun watchosX64(block: Action<KotlinNativeTarget>? = null): KotlinNativeTarget? {
562 supportedPlatforms.add(PlatformIdentifier.WATCHOS_X_64)
563 return if (project.enableMac()) {
564 kotlinExtension.watchosX64 { block?.execute(this) }
565 } else {
566 null
567 }
568 }
569
570 @JvmOverloads
571 fun watchosSimulatorArm64(block: Action<KotlinNativeTarget>? = null): KotlinNativeTarget? {
572 supportedPlatforms.add(PlatformIdentifier.WATCHOS_SIMULATOR_ARM_64)
573 return if (project.enableMac()) {
574 kotlinExtension.watchosSimulatorArm64 { block?.execute(this) }
575 } else {
576 null
577 }
578 }
579
580 /** Configures all tvos targets supported by AndroidX. */
581 @JvmOverloads
582 fun tvos(block: Action<KotlinNativeTarget>? = null): List<KotlinNativeTarget> {
583 return listOfNotNull(tvosX64(block), tvosArm64(block), tvosSimulatorArm64(block))
584 }
585
586 @JvmOverloads
587 fun tvosArm64(block: Action<KotlinNativeTarget>? = null): KotlinNativeTarget? {
588 supportedPlatforms.add(PlatformIdentifier.TVOS_ARM_64)
589 return if (project.enableMac()) {
590 kotlinExtension.tvosArm64 { block?.execute(this) }
591 } else {
592 null
593 }
594 }
595
596 @JvmOverloads
597 fun tvosX64(block: Action<KotlinNativeTarget>? = null): KotlinNativeTarget? {
598 supportedPlatforms.add(PlatformIdentifier.TVOS_X_64)
599 return if (project.enableMac()) {
600 kotlinExtension.tvosX64 { block?.execute(this) }
601 } else {
602 null
603 }
604 }
605
606 @JvmOverloads
607 fun tvosSimulatorArm64(block: Action<KotlinNativeTarget>? = null): KotlinNativeTarget? {
608 supportedPlatforms.add(PlatformIdentifier.TVOS_SIMULATOR_ARM_64)
609 return if (project.enableMac()) {
610 kotlinExtension.tvosSimulatorArm64 { block?.execute(this) }
611 } else {
612 null
613 }
614 }
615
616 @JvmOverloads
617 fun linux(block: Action<KotlinNativeTarget>? = null): List<KotlinNativeTarget> {
618 return listOfNotNull(
619 linuxArm64(block),
620 linuxX64(block),
621 )
622 }
623
624 @JvmOverloads
625 fun linuxArm64(block: Action<KotlinNativeTarget>? = null): KotlinNativeTarget? {
626 supportedPlatforms.add(PlatformIdentifier.LINUX_ARM_64)
627 return if (project.enableLinux()) {
628 kotlinExtension.linuxArm64 { block?.execute(this) }
629 } else {
630 null
631 }
632 }
633
634 @JvmOverloads
635 fun linuxX64(block: Action<KotlinNativeTarget>? = null): KotlinNativeTarget? {
636 supportedPlatforms.add(PlatformIdentifier.LINUX_X_64)
637 return if (project.enableLinux()) {
638 kotlinExtension.linuxX64 { block?.execute(this) }
639 } else {
640 null
641 }
642 }
643
644 @JvmOverloads
645 fun linuxX64Stubs(block: Action<KotlinNativeTarget>? = null): KotlinNativeTarget? {
646 supportedPlatforms.add(PlatformIdentifier.LINUX_X_64_STUBS)
647 return if (project.enableLinux()) {
648 kotlinExtension.linuxX64("linuxx64Stubs") {
649 block?.execute(this)
650 project.tasks.named("linuxx64StubsTest").configure {
651 // don't try running common tests for stubs target
652 it.enabled = false
653 }
654 }
655 } else {
656 null
657 }
658 }
659
660 @JvmOverloads
661 fun js(block: Action<KotlinJsTargetDsl>? = null): KotlinJsTargetDsl? {
662 if (buildFeatures.isIsolatedProjectsEnabled()) return null
663 supportedPlatforms.add(PlatformIdentifier.JS)
664 return if (project.enableJs()) {
665 kotlinExtension.js() {
666 block?.execute(this)
667 binaries.library()
668 project.configureJs()
669 project.configureKotlinJsTests()
670 configureBrowserForTests(project)
671 }
672 } else {
673 null
674 }
675 }
676
677 @OptIn(ExperimentalWasmDsl::class)
678 @JvmOverloads
679 fun wasmJs(block: Action<KotlinJsTargetDsl>? = null): KotlinWasmTargetDsl? {
680 if (buildFeatures.isIsolatedProjectsEnabled()) return null
681 supportedPlatforms.add(PlatformIdentifier.WASM_JS)
682 return if (project.enableWasmJs()) {
683 kotlinExtension.wasmJs("wasmJs") {
684 block?.execute(this)
685 binaries.library()
686 project.configureWasm()
687 project.configureKotlinJsTests()
688 configureBrowserForTests(project)
689 }
690 } else {
691 null
692 }
693 }
694
695 private fun KotlinJsTargetDsl.configureBrowserForTests(project: Project) {
696 browser {
697 testTask {
698 it.useKarma {
699 useChromeHeadless()
700 useConfigDirectory(File(project.getSupportRootFolder(), "buildSrc/karmaconfig"))
701 }
702 }
703 }
704 }
705
706 /** Locates a project by path. */
707 // This method is needed for Gradle project isolation to avoid calls to parent projects due to
708 // androidx { samples(project(":foo")) }
709 // Without this method, the call above results into a call to the parent object, because
710 // AndroidXExtension has `val project: Project`, which from groovy `project` call within
711 // `androidx` block tries retrieves that project object and calls to look for :foo property
712 // on it, then checking all the parents for it.
713 fun project(name: String): Project = project.project(name)
714
715 companion object {
716 const val EXTENSION_NAME = "androidXMultiplatform"
717 }
718 }
719
Projectnull720 private fun Project.configureJs() {
721 configureNode()
722 configureBinaryen()
723 // Use DSL API when https://youtrack.jetbrains.com/issue/KT-70029 is closed for all tasks below
724 tasks.named("jsDevelopmentLibraryCompileSync", DefaultIncrementalSyncTask::class.java) {
725 it.destinationDirectory.set(file(layout.buildDirectory.dir("js/packages/js/dev/kotlin")))
726 }
727 tasks.named("jsProductionLibraryCompileSync", DefaultIncrementalSyncTask::class.java) {
728 it.destinationDirectory.set(file(layout.buildDirectory.dir("js/packages/js/prod/kotlin")))
729 }
730 }
731
Projectnull732 private fun Project.configureWasm() {
733 configureNode()
734 configureBinaryen()
735 // Use DSL API when https://youtrack.jetbrains.com/issue/KT-70029 is closed for all tasks below
736 tasks.named("wasmJsDevelopmentLibraryCompileSync", DefaultIncrementalSyncTask::class.java) {
737 it.destinationDirectory.set(
738 file(layout.buildDirectory.dir("js/packages/wasm-js/dev/kotlin"))
739 )
740 }
741 tasks.named("wasmJsProductionLibraryCompileSync", DefaultIncrementalSyncTask::class.java) {
742 it.destinationDirectory.set(
743 file(layout.buildDirectory.dir("js/packages/wasm-js/prod/kotlin"))
744 )
745 }
746
747 // Compiler Arg needed for tests only: https://youtrack.jetbrains.com/issue/KT-59081
748 tasks.withType(Kotlin2JsCompile::class.java).configureEach { task ->
749 if (task.name.lowercase().contains("test")) {
750 task.compilerOptions.freeCompilerArgs.add("-Xwasm-enable-array-range-checks")
751 }
752 }
753 }
754
Projectnull755 private fun Project.configureNode() {
756 extensions.findByType<NodeJsEnvSpec>()?.let { nodeJs ->
757 nodeJs.version.set(getVersionByName("node"))
758 if (!ProjectLayoutType.isPlayground(this)) {
759 nodeJs.downloadBaseUrl.set(
760 File(project.getPrebuiltsRoot(), "androidx/external/org/nodejs/node")
761 .toURI()
762 .toString()
763 )
764 }
765 }
766
767 // https://youtrack.jetbrains.com/issue/KT-73913/K-Wasm-yarn-version-per-project
768 rootProject.extensions.findByType(YarnRootExtension::class.java)?.let { yarn ->
769 @Suppress("DEPRECATION")
770 yarn.version = getVersionByName("yarn")
771 yarn.yarnLockMismatchReport = YarnLockMismatchReport.FAIL
772 if (!ProjectLayoutType.isPlayground(this)) {
773 yarn.lockFileDirectory =
774 File(project.getPrebuiltsRoot(), "androidx/javascript-for-kotlin")
775 }
776 }
777 }
778
Projectnull779 private fun Project.configureBinaryen() {
780 // https://youtrack.jetbrains.com/issue/KT-74840
781 rootProject.extensions.findByType<BinaryenRootExtension>()?.let { binaryen ->
782 @Suppress("DEPRECATION")
783 binaryen.downloadBaseUrl =
784 File(project.getPrebuiltsRoot(), "androidx/javascript-for-kotlin/binaryen")
785 .toURI()
786 .toString()
787 }
788 }
789
Projectnull790 private fun Project.configureKotlinJsTests() =
791 tasks.withType(KotlinJsTest::class.java).configureEach { task ->
792 if (!ProjectLayoutType.isPlayground(this)) {
793 val unzipChromeBuildServiceProvider =
794 gradle.sharedServices.registrations.getByName("unzipChrome").service
795 task.usesService(unzipChromeBuildServiceProvider)
796 // Remove doFirst and switch to FileProperty property to set browser path when issue
797 // https://youtrack.jetbrains.com/issue/KT-72514 is resolved
798 task.doFirst {
799 task.environment(
800 "CHROME_BIN",
801 (unzipChromeBuildServiceProvider.get() as UnzipChromeBuildService).chromePath
802 )
803 }
804 }
805 task.testLogging.showStandardStreams = true
806 // From: https://nodejs.org/api/cli.html
807 task.nodeJsArgs.addAll(listOf("--trace-warnings", "--trace-uncaught", "--trace-sigint"))
808 }
809
Projectnull810 fun Project.validatePublishedMultiplatformHasDefault() {
811 val extension = project.extensions.getByType(AndroidXMultiplatformExtension::class.java)
812 if (extension.defaultPlatform == null && extension.supportedPlatforms.isNotEmpty()) {
813 throw GradleException(
814 "Project is published and multiple platforms are requested. You " +
815 "must explicitly specify androidXMultiplatform.defaultPlatform as one of: " +
816 extension.targetPlatforms.joinToString(", ") {
817 "PlatformIdentifier.${PlatformIdentifier.fromId(it)!!.name}"
818 }
819 )
820 }
821 }
822