Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ class JetBrainsAndroidXImplPlugin @Inject constructor(
project.changeMavenCoordinatesToJetBrains()
project.configureMavenArtifactUpload(componentFactory)
project.configureDependencyVerification()
project.configureSkikoAwtRuntimeConstraints()
project.plugins.all { plugin ->
if (plugin is KotlinMultiplatformPluginWrapper) {
onKotlinMultiplatformPluginApplied(project)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2026 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.jetbrains.androidx.build

import org.gradle.api.Project
import org.gradle.api.artifacts.DependencySubstitution
import org.gradle.api.artifacts.component.ModuleComponentSelector
import org.gradle.nativeplatform.MachineArchitecture
import org.gradle.nativeplatform.OperatingSystemFamily
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform

fun Project.configureSkikoAwtRuntimeConstraints() {
val currentOs = DefaultNativePlatform.getCurrentOperatingSystem()
val currentArch = DefaultNativePlatform.getCurrentArchitecture()

val platformSuffix = when {
currentOs.isMacOsX && currentArch.isArm64 -> "macos-arm64"
currentOs.isMacOsX && currentArch.isAmd64 -> "macos-x64"
currentOs.isLinux && currentArch.isArm64 -> "linux-arm64"
currentOs.isLinux && currentArch.isAmd64 -> "linux-x64"
currentOs.isWindows && currentArch.isArm64 -> "windows-arm64"
currentOs.isWindows && currentArch.isAmd64 -> "windows-x64"
else -> error("Unsupported platform: OS=${currentOs.name}, Arch=${currentArch.name}")
}

// Use dependency substitution to replace the universal dependency with platform-specific one
// during resolution, without affecting what gets published
project.configurations.configureEach { configuration ->
if (configuration.isCanBeResolved) {
configuration.resolutionStrategy.dependencySubstitution {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use dependencyConstraints as in the CMP plugin to be consistent in the logic?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, not.
Dependency constraints are propagated to published artifacts when configuration is consumable, and it is.
This means that all users will get linux/x86 requirement, if we are building on such a machine.
That's the best solution I've come up so far.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that we also need to use dependencySubstitutution in the plugin, otherwise libraries built with "Compose Multiplatform plugin" have this constraint?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These constraints are only applied to skiko-awt-runtime module, and it is only added as dependency in applications, it is not included in libraries' dependencies. This works so far. And there is a property to disable this behavior completely.
If we rework dependency as suggested in another comment, add skiko-awt-runtime will be added to all libraries, than it might be an issue.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about it a little, and I see that if want to implement hiding this dependency from users (I see that it is beneficial), we can:

  • use dependencySubstitutution in the Gradle plugin
  • don't publish any additional modules on Compose side
  • and it seems there is no point in skiko-awt-runtime, and we can apply dependency substitution on the skiko artifact itself? If this is true, we can remove this artifact from Skiko publication?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use dependencySubstitutution in the Gradle plugin

It looks like from user perspective it works the same? I.e. it allows running applications, and doesn't add anything to the library publications?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we rely on dependencySubstitution, we introduce hard dependency on Compose Gradle plugin.
This will limit us in the future in supporting other build systems.
Current proposed approach with skiko-awt-runtime is less limiting, as the required attributes can be provided manually. Well, after writing this, I realize that there is still a limitation and it is only marginally better.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolutionStrategy is local logic that affects only this project. If it's intended, please make it more explicit + link/explanation in comments how it's resolved for published artifacts

it.all { substitution ->
val requested = substitution.requested
if (requested is ModuleComponentSelector &&
requested.group == "org.jetbrains.skiko" &&
requested.module == "skiko-awt-runtime") {
// Keep the same version, just change the module name to platform-specific
substitution.useTarget(
"${requested.group}:skiko-awt-runtime-${platformSuffix}:${requested.version}",
"Platform-specific variant selection based on current machine"
)
}
}
}
}
}
}
65 changes: 0 additions & 65 deletions buildSrc/settingsScripts/skiko-setup.groovy

This file was deleted.

81 changes: 58 additions & 23 deletions compose/desktop/desktop/build.gradle
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Direct :compose:material:material dependency is still here, so CMP-5990 won't be resolved

Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@
* limitations under the License.
*/


import androidx.build.AndroidXConfig
import androidx.build.SoftwareType
import org.gradle.api.internal.component.SoftwareComponentInternal
import org.gradle.api.internal.component.UsageContext

plugins {
id("AndroidXPlugin")
Expand All @@ -30,22 +33,21 @@ androidXMultiplatform {
commonMain.dependencies {
implementation(project(":compose:ui:ui-util"))
api(project(":compose:foundation:foundation"))
api(project(":compose:material:material"))
api(project(":compose:runtime:runtime"))
api(project(":compose:ui:ui"))
api(project(":compose:ui:ui-tooling-preview"))
}

jvmMain.dependencies {
implementation(libs.kotlinCoroutinesCore)
implementation(libs.skikoAwtRuntime)
}

jvmTest {
resources.srcDirs += new File(AndroidXConfig.getExternalProjectPath(project), "noto-fonts/other/")
resources.srcDirs += "src/jvmTest/res"
dependencies {
implementation(libs.kotlinCoroutinesTest)
implementation(libs.skikoCurrentOs)
implementation(project(":compose:ui:ui-test-junit4"))
implementation(libs.junit)
implementation(libs.truth)
Expand Down Expand Up @@ -75,29 +77,62 @@ androidx {
}

def jvmOs(container, name, skikoDep) {
def projectGroup = project.group
def projectName = project.name
def composeVersion = project.version
def skikoModule = skikoDep.module
def skikoVersion = skikoDep.versionConstraint.requiredVersion

def desktopDep = dependencies.create("${projectGroup}:${projectName}:${composeVersion}") {
exclude group: 'org.jetbrains.skiko', module: 'skiko-awt-runtime'
}
def skikoRuntimeDep = dependencies.create("${skikoModule.group}:${skikoModule.name}:${skikoVersion}")

def apiElements = configurations.create("jvm${name}ApiElements") {
canBeResolved = false
canBeConsumed = true
attributes {
attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_API))
}
outgoing.capability("${project.group}:${project.name}-jvm-${name}:${project.version}")
}
def runtimeElements = configurations.create("jvm${name}RuntimeElements") {
canBeResolved = false
canBeConsumed = true
attributes {
attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME))
}
outgoing.capability("${project.group}:${project.name}-jvm-${name}:${project.version}")
}

def apiUsage = [
getName: { -> apiElements.name },
getArtifacts: { -> [] as Set },
getAttributes: { -> apiElements.attributes },
getCapabilities: { -> [] as Set },
getDependencies: { -> [desktopDep] as Set },
getDependencyConstraints: { -> [] as Set },
getGlobalExcludes: { -> [] as Set },
] as UsageContext

def runtimeUsage = [
getName: { -> runtimeElements.name },
getArtifacts: { -> [] as Set },
getAttributes: { -> runtimeElements.attributes },
getCapabilities: { -> [] as Set },
getDependencies: { -> [desktopDep, skikoRuntimeDep] as Set },
getDependencyConstraints: { -> [] as Set },
getGlobalExcludes: { -> [] as Set },
] as UsageContext

def component = [
getName: { -> "jvm${name}" },
getUsages: { -> [apiUsage, runtimeUsage] as Set },
] as SoftwareComponentInternal

container.create("jvm$name", MavenPublication) {
artifactId = "${project.name}-jvm-$name"
def projectGroup = project.group
def projectName = project.name
def composeVersion = project.version
def skikoModule = skikoDep.module
def skikoVersion = skikoDep.versionConstraint.requiredVersion
pom {
withXml {
def dependenciesNode = asNode().appendNode("dependencies")
def desktopDependency = dependenciesNode.appendNode("dependency")
desktopDependency.appendNode("groupId", projectGroup)
desktopDependency.appendNode("artifactId", projectName)
desktopDependency.appendNode("version", composeVersion)
desktopDependency.appendNode("scope", "compile")

def skikoDependency = dependenciesNode.appendNode("dependency")
skikoDependency.appendNode("groupId", skikoModule.group)
skikoDependency.appendNode("artifactId", skikoModule.name)
skikoDependency.appendNode("version", skikoVersion)
skikoDependency.appendNode("scope", "runtime")
}
}
from(component)
}
}

Expand Down
2 changes: 1 addition & 1 deletion compose/desktop/desktop/samples-material3/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ kotlin {
}

jvmMain.dependencies {
implementation(libs.skikoCurrentOs)
implementation(libs.skikoAwtRuntime)
implementation(project(":compose:material3:material3"))
implementation(project(":compose:desktop:desktop"))
}
Expand Down
5 changes: 2 additions & 3 deletions compose/desktop/desktop/samples/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,9 @@ kotlin {
resources.srcDirs += "src/jvmMain/res"

dependencies {
implementation(libs.skikoCurrentOs)
implementation(project(":collection:collection"))
implementation(project(":collection:collection"))
implementation(project(":compose:desktop:desktop"))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we rename this module to compose:ui-awt?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please elaborate which exactly module you are proposing to rename?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • rename :compose:desktop:desktop to compose:ui-awt (because it belong more to ui, and similar to ui-uikit)
  • remove any dependencies from ui-awt except skiko
  • add ui -> ui-awt dependency to hide this module from users (see my other comment)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does that mean that every library would have it as dependency, I've assumed we don't want this.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've assumed we don't want this.

Probably we want, because the org.jetbrains.skiko:skiko on the desktop target always assumes that the runtime dependency is present, can't work without them, and can't use something different. The org.jetbrains.skiko:skiko-awt-runtime dependency itself still doesn't point to a specific OS.

Copy link
Collaborator

@igordmn igordmn Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of that, maybe we even don't need extra dependencies in Compose, and just make org.jetbrains.skiko:skiko -> org.jetbrains.skiko:skiko-awt-runtime in the desktop target in Skiko?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we don't require users to depend on this explicitly, and add transitive dependency ui -> ui-awt in desktopMain?

So, when users use the CMP plugin and compose.ui, they receive the right dependency.


implementation(project(":compose:material:material"))
implementation("org.jetbrains.compose.material:material-icons-core:1.7.3") {
// exclude dependencies, because they override local projects when we build 0.0.0-* version
// (see https://repo1.maven.org/maven2/org/jetbrains/compose/material/material-icons-core-desktop/1.6.11/material-icons-core-desktop-1.6.11.module)
Expand Down

This file was deleted.

2 changes: 1 addition & 1 deletion compose/foundation/foundation/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ androidXMultiplatform {
implementation(project(":compose:ui:ui-test-junit4"))
implementation(libs.truth)
implementation(libs.junit)
implementation(libs.skikoCurrentOs)
implementation(libs.skikoAwtRuntime)
implementation(libs.kotlinCoroutinesSwing)
implementation(libs.mockitoCore4)
implementation(libs.mockitoKotlin4)
Expand Down
2 changes: 1 addition & 1 deletion compose/material/material/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ androidXMultiplatform {
implementation(project(":compose:ui:ui-test-junit4"))
implementation(libs.truth)
implementation(libs.junit)
implementation(libs.skikoCurrentOs)
implementation(libs.skikoAwtRuntime)
}
}

Expand Down
2 changes: 1 addition & 1 deletion compose/material3/material3/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ androidXMultiplatform {
implementation(project(":compose:ui:ui-test-junit4"))
implementation(libs.truth)
implementation(libs.junit)
implementation(libs.skikoCurrentOs)
implementation(libs.skikoAwtRuntime)
}
}

Expand Down
2 changes: 1 addition & 1 deletion compose/mpp/demo/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ kotlin {
dependsOn(skikoMain)
dependencies {
implementation(libs.kotlinCoroutinesSwing)
implementation(libs.skikoCurrentOs)
implementation(libs.skikoAwtRuntime)
}
}

Expand Down
Loading