Browse Source
## Testing Clone https://github.com/Schahen/ComposeWebApp and run ``` ./gradlew :composeApp: composeCompatibilityBrowserDistribution ``` ## Release Notes ### Features - Web - Introduce composeCompatibilityBrowserDistribution task. This task combines two prod distributions - for js and for wasm in such way so that if modern require features are not supported by the consumer browser, application switch to js mode. --------- Co-authored-by: Oleksandr Karpovich <a.n.karpovich@gmail.com> Co-authored-by: Konstantin Tskhovrebov <konstantin.tskhovrebov@jetbrains.com>pull/5377/head v1.9.0+dev2724
28 changed files with 574 additions and 3 deletions
@ -0,0 +1,173 @@
@@ -0,0 +1,173 @@
|
||||
package org.jetbrains.compose.web.tasks |
||||
|
||||
import org.gradle.api.DefaultTask |
||||
import org.gradle.api.Project |
||||
import org.gradle.api.file.ConfigurableFileCollection |
||||
import org.gradle.api.file.DirectoryProperty |
||||
import org.gradle.api.file.DuplicatesStrategy |
||||
import org.gradle.api.file.FileSystemOperations |
||||
import org.gradle.api.provider.Property |
||||
import org.gradle.api.tasks.Input |
||||
import org.gradle.api.tasks.InputFiles |
||||
import org.gradle.api.tasks.Optional |
||||
import org.gradle.api.tasks.OutputDirectory |
||||
import org.gradle.api.tasks.TaskAction |
||||
import org.jetbrains.compose.internal.KOTLIN_MPP_PLUGIN_ID |
||||
import org.jetbrains.compose.internal.mppExt |
||||
import org.jetbrains.compose.internal.utils.clearDirs |
||||
import org.jetbrains.compose.internal.utils.joinLowerCamelCase |
||||
import org.jetbrains.compose.internal.utils.registerTask |
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension |
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType |
||||
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget |
||||
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpack |
||||
import java.io.File |
||||
import javax.inject.Inject |
||||
|
||||
abstract class WebCompatibilityTask : DefaultTask() { |
||||
@get:Inject |
||||
internal abstract val fileOperations: FileSystemOperations |
||||
|
||||
@get:OutputDirectory |
||||
abstract val outputDir: DirectoryProperty |
||||
|
||||
@get:InputFiles |
||||
abstract val jsDistFiles: ConfigurableFileCollection |
||||
|
||||
@get:InputFiles |
||||
abstract val wasmDistFiles: ConfigurableFileCollection |
||||
|
||||
@get:Input |
||||
@get:Optional |
||||
abstract val jsOutputName: Property<String> |
||||
|
||||
@get:Input |
||||
@get:Optional |
||||
abstract val wasmOutputName: Property<String> |
||||
|
||||
@TaskAction |
||||
fun run() { |
||||
val prefix = "origin" |
||||
val jsAppFileName = jsOutputName.get() |
||||
val jsAppRenamed = joinLowerCamelCase(prefix, "js", jsAppFileName) |
||||
val wasmAppFileName = wasmOutputName.get() |
||||
val wasmAppRenamed = joinLowerCamelCase(prefix, "wasm", wasmAppFileName) |
||||
|
||||
fileOperations.clearDirs(outputDir) |
||||
|
||||
fileOperations.copy { copySpec -> |
||||
copySpec.duplicatesStrategy = DuplicatesStrategy.WARN |
||||
|
||||
copySpec.from(jsDistFiles) { |
||||
it.rename { name -> |
||||
when (name) { |
||||
jsAppFileName -> jsAppRenamed |
||||
"${jsAppFileName}.map" -> "${jsAppRenamed}.map" |
||||
else -> name |
||||
} |
||||
} |
||||
} |
||||
copySpec.from(wasmDistFiles) { |
||||
it.rename { name -> |
||||
when (name) { |
||||
wasmAppFileName -> wasmAppRenamed |
||||
"${wasmAppFileName}.map" -> "${wasmAppRenamed}.map" |
||||
else -> name |
||||
} |
||||
} |
||||
} |
||||
|
||||
copySpec.into(outputDir) |
||||
} |
||||
|
||||
val fallbackResolverCode = """ |
||||
const simpleWasmModule = new Uint8Array([ |
||||
0, 97, 115, 109, 1, 0, 0, 0, 1, 8, 2, 95, |
||||
1, 120, 0, 96, 0, 0, 3, 3, 2, 1, 1, 10, |
||||
14, 2, 6, 0, 6, 64, 25, 11, 11, 5, 0, 208, |
||||
112, 26, 11, 0, 45, 4, 110, 97, 109, 101, 1, 15, |
||||
2, 0, 5, 102, 117, 110, 99, 48, 1, 5, 102, 117, |
||||
110, 99, 49, 4, 8, 1, 0, 5, 116, 121, 112, 101, |
||||
48, 10, 11, 1, 0, 1, 0, 6, 102, 105, 101, 108, |
||||
100, 48 |
||||
]); |
||||
|
||||
const hasSupportOfAllRequiredWasmFeatures = () => |
||||
typeof WebAssembly !== "undefined" && |
||||
typeof WebAssembly?.validate === "function" && |
||||
WebAssembly.validate(simpleWasmModule); |
||||
|
||||
const createScript = (src) => { |
||||
const script = document.createElement("script"); |
||||
script.src = src; |
||||
script.type = "application/javascript"; |
||||
return script; |
||||
} |
||||
|
||||
if (hasSupportOfAllRequiredWasmFeatures()) { |
||||
document.body.appendChild(createScript('$wasmAppRenamed')); |
||||
} else { |
||||
document.body.appendChild(createScript('$jsAppRenamed')); |
||||
} |
||||
|
||||
""".trimIndent() |
||||
|
||||
|
||||
val outputDir = outputDir.get().asFile |
||||
File(outputDir, jsAppFileName).writeText(fallbackResolverCode) |
||||
File(outputDir, wasmAppFileName).writeText(fallbackResolverCode) |
||||
} |
||||
} |
||||
|
||||
private fun Project.registerWebCompatibilityTask(mppPlugin: KotlinMultiplatformExtension) = |
||||
registerTask<WebCompatibilityTask>("composeCompatibilityBrowserDistribution") { |
||||
group = "compose" |
||||
description = |
||||
"This task combines both js and wasm distributions into one so that wasm application fallback to js target if modern wasm feature are not supported" |
||||
|
||||
val webProductionDist = layout.buildDirectory.dir("dist/composeWebCompatibility/productionExecutable") |
||||
outputDir.set(webProductionDist) |
||||
|
||||
mppPlugin.targets.withType(KotlinJsIrTarget::class.java).configureEach { target -> |
||||
if (target.platformType == KotlinPlatformType.wasm) { |
||||
wasmOutputName.set( |
||||
tasks.named( |
||||
"${target.name}BrowserProductionWebpack", |
||||
KotlinWebpack::class.java |
||||
).flatMap { it.mainOutputFileName } |
||||
) |
||||
wasmDistFiles.from( |
||||
tasks.named("${target.name}BrowserDistribution").map { it.outputs.files } |
||||
) |
||||
} else if (target.platformType == KotlinPlatformType.js) { |
||||
jsOutputName.set( |
||||
tasks.named( |
||||
"${target.name}BrowserProductionWebpack", |
||||
KotlinWebpack::class.java |
||||
).flatMap { it.mainOutputFileName } |
||||
) |
||||
jsDistFiles.from( |
||||
tasks.named("${target.name}BrowserDistribution").map { it.outputs.files } |
||||
) |
||||
} |
||||
|
||||
onlyIf { |
||||
val hasBothDistributions = !jsDistFiles.isEmpty && !wasmDistFiles.isEmpty |
||||
val hasBothOutputs = jsOutputName.orNull != null && wasmOutputName.orNull != null |
||||
|
||||
if (!hasBothDistributions) { |
||||
logger.lifecycle("Task ${this.name} skipped: no js and wasm distributions found, both are required for compatibility") |
||||
} else if (!hasBothOutputs) { |
||||
logger.lifecycle("Task ${this.name} skipped: no js and wasm output names specified") |
||||
} |
||||
hasBothDistributions && hasBothOutputs |
||||
} |
||||
|
||||
} |
||||
} |
||||
|
||||
internal fun Project.configureWebCompatibility() { |
||||
plugins.withId(KOTLIN_MPP_PLUGIN_ID) { |
||||
project.registerWebCompatibilityTask(mppExt) |
||||
} |
||||
} |
||||
@ -0,0 +1,66 @@
@@ -0,0 +1,66 @@
|
||||
package org.jetbrains.compose.test.tests.integration |
||||
|
||||
import org.gradle.internal.impldep.junit.framework.TestCase.assertTrue |
||||
import org.jetbrains.compose.test.utils.GradlePluginTestBase |
||||
import org.jetbrains.compose.test.utils.checkExists |
||||
import org.jetbrains.compose.test.utils.checks |
||||
import org.junit.jupiter.api.Test |
||||
import kotlin.test.assertContentEquals |
||||
|
||||
class CompatibilityDistributionTest : GradlePluginTestBase() { |
||||
@Test |
||||
fun testWebJsWasm() = with( |
||||
testProject( |
||||
"application/webJsWasm", |
||||
testEnvironment = defaultTestEnvironment.copy() |
||||
) |
||||
) { |
||||
gradle(":composeApp:composeCompatibilityBrowserDistribution").checks { |
||||
check.taskSuccessful(":composeApp:composeCompatibilityBrowserDistribution") |
||||
check.taskSuccessful(":composeApp:jsBrowserDistribution") |
||||
check.taskSuccessful(":composeApp:wasmJsBrowserDistribution") |
||||
|
||||
file("./composeApp/build/dist/composeWebCompatibility/productionExecutable").apply { |
||||
checkExists() |
||||
assertTrue(isDirectory) |
||||
val distributionFiles = listFiles()!!.map { it.name }.toList().sorted() |
||||
|
||||
assertTrue(distributionFiles.any { it.endsWith(".wasm") }) |
||||
|
||||
assertContentEquals(distributionFiles.filter { !it.endsWith(".wasm") }, listOf( |
||||
"composeApp.js", "composeResources", "index.html", "originJsComposeApp.js", "originJsComposeApp.js.map", "originWasmComposeApp.js", "originWasmComposeApp.js.map", "styles.css" |
||||
)) |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
@Test |
||||
fun testWebJsWasmNonStandard() = with( |
||||
testProject( |
||||
"application/webJsWasmNonStandard", |
||||
testEnvironment = defaultTestEnvironment.copy() |
||||
) |
||||
) { |
||||
gradle(":composeApp:composeCompatibilityBrowserDistribution").checks { |
||||
check.taskSuccessful(":composeApp:composeCompatibilityBrowserDistribution") |
||||
check.taskSuccessful(":composeApp:webJsBrowserDistribution") |
||||
check.taskSuccessful(":composeApp:webWasmBrowserDistribution") |
||||
|
||||
file("./composeApp/build/dist/composeWebCompatibility/productionExecutable").apply { |
||||
checkExists() |
||||
assertTrue(isDirectory) |
||||
val distributionFiles = listFiles()!!.map { it.name }.toList().sorted() |
||||
|
||||
assertTrue(distributionFiles.any { it.endsWith(".wasm") }) |
||||
|
||||
println(distributionFiles.filter { !it.endsWith(".wasm") }.sorted().joinToString(", ") { "\"$it\"" }) |
||||
|
||||
assertContentEquals(distributionFiles.filter { !it.endsWith(".wasm") }, listOf( |
||||
"composeApp.js", "composeResources", "index.html", "originJsComposeApp.js", "originJsComposeApp.js.map", "originWasmComposeApp.js", "originWasmComposeApp.js.map", "styles.css" |
||||
)) |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
--install.mutex network |
||||
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
plugins { |
||||
// this is necessary to avoid the plugins to be loaded multiple times |
||||
// in each subproject's classloader |
||||
id("org.jetbrains.kotlin.multiplatform") apply false |
||||
id("org.jetbrains.compose") apply false |
||||
id("org.jetbrains.kotlin.plugin.compose") apply false |
||||
} |
||||
@ -0,0 +1,56 @@
@@ -0,0 +1,56 @@
|
||||
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl |
||||
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig |
||||
|
||||
plugins { |
||||
id("org.jetbrains.kotlin.multiplatform") |
||||
id("org.jetbrains.compose") |
||||
id("org.jetbrains.kotlin.plugin.compose") |
||||
} |
||||
|
||||
kotlin { |
||||
js(IR) { |
||||
outputModuleName.set("composeApp") |
||||
browser { |
||||
commonWebpackConfig { |
||||
outputFileName = "composeApp.js" |
||||
} |
||||
} |
||||
binaries.executable() |
||||
} |
||||
|
||||
@OptIn(ExperimentalWasmDsl::class) |
||||
wasmJs { |
||||
outputModuleName.set("composeApp") |
||||
browser { |
||||
val rootDirPath = project.rootDir.path |
||||
val projectDirPath = project.projectDir.path |
||||
commonWebpackConfig { |
||||
outputFileName = "composeApp.js" |
||||
} |
||||
} |
||||
binaries.executable() |
||||
} |
||||
|
||||
// Simple task that prints "Hello World" to the console |
||||
|
||||
sourceSets { |
||||
commonMain.dependencies { |
||||
implementation(compose.runtime) |
||||
} |
||||
|
||||
val webMain by creating { |
||||
resources.setSrcDirs(resources.srcDirs) |
||||
dependsOn(commonMain.get()) |
||||
} |
||||
|
||||
val jsMain by getting { |
||||
dependsOn(webMain) |
||||
} |
||||
|
||||
val wasmJsMain by getting { |
||||
dependsOn(webMain) |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
package org.example.project |
||||
|
||||
private class JsPlatform : Platform { |
||||
override val name: String = "Web with Kotlin/JS" |
||||
} |
||||
|
||||
actual fun getPlatform(): Platform = JsPlatform() |
||||
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
package org.example.project |
||||
|
||||
private class WasmPlatform : Platform { |
||||
override val name: String = "Web with Kotlin/Wasm" |
||||
} |
||||
|
||||
actual fun getPlatform(): Platform = WasmPlatform() |
||||
@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
|
||||
package org.example.project |
||||
|
||||
class Greeting { |
||||
private val platform = getPlatform() |
||||
|
||||
fun greet(): String { |
||||
return "Hello, ${platform.name}!" |
||||
} |
||||
} |
||||
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
package org.example.project |
||||
|
||||
interface Platform { |
||||
val name: String |
||||
} |
||||
|
||||
expect fun getPlatform(): Platform |
||||
@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
|
||||
package org.example.project |
||||
|
||||
fun main() { |
||||
println(Greeting().greet()) |
||||
} |
||||
@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta charset="UTF-8"> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
<title>KotlinProject</title> |
||||
<link type="text/css" rel="stylesheet" href="styles.css"> |
||||
</head> |
||||
<body> |
||||
</body> |
||||
<script type="application/javascript" src="composeApp.js"></script> |
||||
|
||||
</html> |
||||
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
html, body { |
||||
width: 100%; |
||||
height: 100%; |
||||
margin: 0; |
||||
padding: 0; |
||||
overflow: hidden; |
||||
} |
||||
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
[versions] |
||||
androidx-lifecycle = "2.9.1" |
||||
|
||||
[libraries] |
||||
androidx-lifecycle-viewmodel = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel", version.ref = "androidx-lifecycle" } |
||||
androidx-lifecycle-runtimeCompose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle" } |
||||
@ -0,0 +1,41 @@
@@ -0,0 +1,41 @@
|
||||
rootProject.name = "WebJsMain" |
||||
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") |
||||
|
||||
pluginManagement { |
||||
plugins { |
||||
id("org.jetbrains.kotlin.multiplatform") version "KOTLIN_VERSION_PLACEHOLDER" |
||||
id("org.jetbrains.kotlin.plugin.compose") version "KOTLIN_VERSION_PLACEHOLDER" |
||||
id("org.jetbrains.compose") version "COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER" |
||||
} |
||||
repositories { |
||||
mavenLocal() |
||||
google { |
||||
mavenContent { |
||||
includeGroupAndSubgroups("androidx") |
||||
includeGroupAndSubgroups("com.android") |
||||
includeGroupAndSubgroups("com.google") |
||||
} |
||||
} |
||||
mavenCentral() |
||||
gradlePluginPortal() |
||||
} |
||||
} |
||||
|
||||
dependencyResolutionManagement { |
||||
repositories { |
||||
mavenLocal() |
||||
maven { |
||||
url = uri("https://maven.pkg.jetbrains.space/public/p/compose/dev") |
||||
} |
||||
google { |
||||
mavenContent { |
||||
includeGroupAndSubgroups("androidx") |
||||
includeGroupAndSubgroups("com.android") |
||||
includeGroupAndSubgroups("com.google") |
||||
} |
||||
} |
||||
mavenCentral() |
||||
} |
||||
} |
||||
|
||||
include(":composeApp") |
||||
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
plugins { |
||||
// this is necessary to avoid the plugins to be loaded multiple times |
||||
// in each subproject's classloader |
||||
id("org.jetbrains.kotlin.multiplatform") apply false |
||||
id("org.jetbrains.compose") apply false |
||||
id("org.jetbrains.kotlin.plugin.compose") apply false |
||||
} |
||||
@ -0,0 +1,56 @@
@@ -0,0 +1,56 @@
|
||||
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl |
||||
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig |
||||
|
||||
plugins { |
||||
id("org.jetbrains.kotlin.multiplatform") |
||||
id("org.jetbrains.compose") |
||||
id("org.jetbrains.kotlin.plugin.compose") |
||||
} |
||||
|
||||
kotlin { |
||||
js(name = "webJs", IR) { |
||||
outputModuleName.set("composeApp") |
||||
browser { |
||||
commonWebpackConfig { |
||||
outputFileName = "composeApp.js" |
||||
} |
||||
} |
||||
binaries.executable() |
||||
} |
||||
|
||||
@OptIn(ExperimentalWasmDsl::class) |
||||
wasmJs(name = "webWasm") { |
||||
outputModuleName.set("composeApp") |
||||
browser { |
||||
val rootDirPath = project.rootDir.path |
||||
val projectDirPath = project.projectDir.path |
||||
commonWebpackConfig { |
||||
outputFileName = "composeApp.js" |
||||
} |
||||
} |
||||
binaries.executable() |
||||
} |
||||
|
||||
// Simple task that prints "Hello World" to the console |
||||
|
||||
sourceSets { |
||||
commonMain.dependencies { |
||||
implementation(compose.runtime) |
||||
} |
||||
|
||||
val webMain by creating { |
||||
resources.setSrcDirs(resources.srcDirs) |
||||
dependsOn(commonMain.get()) |
||||
} |
||||
|
||||
val webJsMain by getting { |
||||
dependsOn(webMain) |
||||
} |
||||
|
||||
val webWasmMain by getting { |
||||
dependsOn(webMain) |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
package org.example.project |
||||
|
||||
private class JsPlatform : Platform { |
||||
override val name: String = "Web with Kotlin/JS" |
||||
} |
||||
|
||||
actual fun getPlatform(): Platform = JsPlatform() |
||||
@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
|
||||
package org.example.project |
||||
|
||||
class Greeting { |
||||
private val platform = getPlatform() |
||||
|
||||
fun greet(): String { |
||||
return "Hello, ${platform.name}!" |
||||
} |
||||
} |
||||
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
package org.example.project |
||||
|
||||
interface Platform { |
||||
val name: String |
||||
} |
||||
|
||||
expect fun getPlatform(): Platform |
||||
@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
|
||||
package org.example.project |
||||
|
||||
fun main() { |
||||
println(Greeting().greet()) |
||||
} |
||||
@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta charset="UTF-8"> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
<title>KotlinProject</title> |
||||
<link type="text/css" rel="stylesheet" href="styles.css"> |
||||
</head> |
||||
<body> |
||||
</body> |
||||
<script type="application/javascript" src="composeApp.js"></script> |
||||
|
||||
</html> |
||||
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
html, body { |
||||
width: 100%; |
||||
height: 100%; |
||||
margin: 0; |
||||
padding: 0; |
||||
overflow: hidden; |
||||
} |
||||
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
package org.example.project |
||||
|
||||
private class WasmPlatform : Platform { |
||||
override val name: String = "Web with Kotlin/Wasm" |
||||
} |
||||
|
||||
actual fun getPlatform(): Platform = WasmPlatform() |
||||
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
[versions] |
||||
androidx-lifecycle = "2.9.1" |
||||
|
||||
[libraries] |
||||
androidx-lifecycle-viewmodel = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel", version.ref = "androidx-lifecycle" } |
||||
androidx-lifecycle-runtimeCompose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle" } |
||||
@ -0,0 +1,41 @@
@@ -0,0 +1,41 @@
|
||||
rootProject.name = "WebJsMain" |
||||
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") |
||||
|
||||
pluginManagement { |
||||
plugins { |
||||
id("org.jetbrains.kotlin.multiplatform") version "KOTLIN_VERSION_PLACEHOLDER" |
||||
id("org.jetbrains.kotlin.plugin.compose") version "KOTLIN_VERSION_PLACEHOLDER" |
||||
id("org.jetbrains.compose") version "COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER" |
||||
} |
||||
repositories { |
||||
mavenLocal() |
||||
google { |
||||
mavenContent { |
||||
includeGroupAndSubgroups("androidx") |
||||
includeGroupAndSubgroups("com.android") |
||||
includeGroupAndSubgroups("com.google") |
||||
} |
||||
} |
||||
mavenCentral() |
||||
gradlePluginPortal() |
||||
} |
||||
} |
||||
|
||||
dependencyResolutionManagement { |
||||
repositories { |
||||
mavenLocal() |
||||
maven { |
||||
url = uri("https://maven.pkg.jetbrains.space/public/p/compose/dev") |
||||
} |
||||
google { |
||||
mavenContent { |
||||
includeGroupAndSubgroups("androidx") |
||||
includeGroupAndSubgroups("com.android") |
||||
includeGroupAndSubgroups("com.google") |
||||
} |
||||
} |
||||
mavenCentral() |
||||
} |
||||
} |
||||
|
||||
include(":composeApp") |
||||
Loading…
Reference in new issue