Skip to content

Commit

Permalink
Refactor publication and artifacts redirecting configuration (#1180)
Browse files Browse the repository at this point in the history
With this PR it's much easier to add new modules with redirecting
publication.
I use this approach in my branch for lifecycle publication. But want to
merge it to jb-main first.

Now we have our dedicated `JetbrainsAndroidXPlugin` which configures
`artifacts redirection` when applied (requires that Kotlin MPP plugin is
applied too).
  • Loading branch information
eymar committed Mar 15, 2024
1 parent 1d73c9c commit 73be0df
Show file tree
Hide file tree
Showing 37 changed files with 357 additions and 290 deletions.
3 changes: 3 additions & 0 deletions annotation/annotation/build.gradle
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import androidx.build.AndroidXComposePlugin
import androidx.build.JetbrainsAndroidXPlugin
import androidx.build.LibraryType
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile
Expand All @@ -8,9 +9,11 @@ plugins {

// TODO move all functionality to Compose-independent plugin (`collection` shouldn't depend on Compose concepts)
id("AndroidXComposePlugin")
id("JetbrainsAndroidXPlugin")
}

AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project)
JetbrainsAndroidXPlugin.applyAndConfigure(project)

androidXComposeMultiplatform {
js()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/*
* Copyright 2024 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.
*/

@file:Suppress("unused")

package androidx.build

import androidx.build.jetbrains.artifactRedirecting
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.create
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper
import org.jetbrains.kotlin.gradle.plugin.mpp.AbstractKotlinTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinSoftwareComponentWithCoordinatesAndPublication
import org.jetbrains.kotlin.konan.target.KonanTarget

open class JetbrainsExtensions(
val project: Project,
val multiplatformExtension: KotlinMultiplatformExtension
) {

// check for example here: https://maven.google.com/web/index.html?q=lifecyc#androidx.lifecycle
val defaultKonanTargetsPublishedByAndroidx = setOf(
KonanTarget.LINUX_X64,
KonanTarget.IOS_X64,
KonanTarget.IOS_ARM64,
KonanTarget.IOS_SIMULATOR_ARM64,
KonanTarget.MACOS_X64,
KonanTarget.MACOS_ARM64,
)

@JvmOverloads
fun configureKNativeRedirectingDependenciesInKlibManifest(
konanTargets: Set<KonanTarget> = defaultKonanTargetsPublishedByAndroidx
) {
multiplatformExtension.targets.all {
if (it is KotlinNativeTarget && it.konanTarget in konanTargets) {
it.substituteForRedirectedPublishedDependencies()
}
}
}

/**
* When https://youtrack.jetbrains.com/issue/KT-61096 is implemented,
* this workaround won't be needed anymore:
*
* K/Native stores the dependencies in klib manifest and tries to resolve them during compilation.
* Since we use project dependency - implementation(project(...)), the klib manifest will reference
* our groupId (for example org.jetbrains.compose.collection-internal instead of androidx.collection).
* Therefore, the dependency can't be resolved since we don't publish libs for some k/native targets.
*
* To workaround that, we need to make sure
* that the project dependency is substituted by a module dependency (from androidx).
* We do this here. It should be called only for those k/native targets which require
* redirection to androidx artefacts.
*
* For available androidx targets see:
* https://maven.google.com/web/index.html#androidx.annotation
* https://maven.google.com/web/index.html#androidx.collection
* https://maven.google.com/web/index.html#androidx.lifecycle
*/
fun KotlinNativeTarget.substituteForRedirectedPublishedDependencies() {
val comp = compilations.getByName("main")
val androidAnnotationVersion =
project.findProperty("artifactRedirecting.androidx.annotation.version")!!
val androidCollectionVersion =
project.findProperty("artifactRedirecting.androidx.collection.version")!!
val androidLifecycleVersion =
project.findProperty("artifactRedirecting.androidx.lifecycle.version")
listOf(
comp.configurations.compileDependencyConfiguration,
comp.configurations.runtimeDependencyConfiguration,
comp.configurations.apiConfiguration,
comp.configurations.implementationConfiguration,
comp.configurations.runtimeOnlyConfiguration,
comp.configurations.compileOnlyConfiguration,
).forEach { c ->
c?.resolutionStrategy {
it.dependencySubstitution {
it.substitute(it.project(":annotation:annotation"))
.using(it.module("androidx.annotation:annotation:$androidAnnotationVersion"))
it.substitute(it.project(":collection:collection"))
.using(it.module("androidx.collection:collection:$androidCollectionVersion"))
if (androidLifecycleVersion != null) {
it.substitute(it.project(":lifecycle:lifecycle-common"))
.using(it.module("androidx.lifecycle:lifecycle-common:$androidLifecycleVersion"))
it.substitute(it.project(":lifecycle:lifecycle-runtime"))
.using(it.module("androidx.lifecycle:lifecycle-runtime:$androidLifecycleVersion"))
}
}
}
}
}

}
class JetbrainsAndroidXPlugin : Plugin<Project> {

@Suppress("UNREACHABLE_CODE", "UNUSED_VARIABLE")
override fun apply(project: Project) {
project.plugins.all { plugin ->
if (plugin is KotlinMultiplatformPluginWrapper) {
onKotlinMultiplatformPluginApplied(project)
}
}
}

private fun onKotlinMultiplatformPluginApplied(project: Project) {
enableArtifactRedirectingPublishing(project)
val multiplatformExtension =
project.extensions.getByType(KotlinMultiplatformExtension::class.java)

val extension = project.extensions.create<JetbrainsExtensions>(
"jetbrainsExtension",
project,
multiplatformExtension
)

// Note: Currently we call it unconditionally since Androidx provides the same set of
// Konan targets for all multiplatform libs they publish.
// In the future we might need to call it with non-default konan targets set in some modules
extension.configureKNativeRedirectingDependenciesInKlibManifest()
}

companion object {

@Suppress("UNUSED_PARAMETER")
@JvmStatic
fun applyAndConfigure(
project: Project
) {}
}
}

private val Project.multiplatformExtension
get() = extensions.findByType(KotlinMultiplatformExtension::class.java)

fun Project.experimentalArtifactRedirectingPublication() : Boolean = findProperty("artifactRedirecting.publication") == "true"
fun Project.artifactRedirectingAndroidxVersion() : String? = findProperty("artifactRedirecting.androidx.compose.version") as String?
fun Project.artifactRedirectingAndroidxFoundationVersion() : String? = findProperty("artifactRedirecting.androidx.compose.foundation.version") as String?
fun Project.artifactRedirectingAndroidxMaterial3Version() : String? = findProperty("artifactRedirecting.androidx.compose.material3.version") as String?
fun Project.artifactRedirectingAndroidxMaterialVersion() : String? = findProperty("artifactRedirecting.androidx.compose.material.version") as String?

fun enableArtifactRedirectingPublishing(project: Project) {
if (!project.experimentalArtifactRedirectingPublication()) return

if (project.experimentalArtifactRedirectingPublication() && (project.artifactRedirectingAndroidxVersion() == null)) {
error("androidx version should be specified for OEL publications")
}

val ext = project.multiplatformExtension ?: error("expected a multiplatform project")

val redirecting = project.artifactRedirecting()
val newRootComponent: CustomRootComponent = run {
val rootComponent = project
.components
.withType(KotlinSoftwareComponentWithCoordinatesAndPublication::class.java)
.getByName("kotlin")

val newDependency = project.dependencies.create(redirecting.groupId, project.name, redirecting.version)
CustomRootComponent(rootComponent, newDependency)
}

val oelTargetNames = (project.findProperty("artifactRedirecting.publication.targetNames") as? String ?: "")
.split(",").toSet()

ext.targets.all { target ->
if (target.name in oelTargetNames || target is KotlinAndroidTarget) {
project.publishAndroidxReference(target as AbstractKotlinTarget, newRootComponent)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 The Android Open Source Project
* Copyright 2024 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.
Expand Down Expand Up @@ -58,7 +58,7 @@ internal class CustomUsage(
}

@OptIn(InternalKotlinGradlePluginApi::class)
internal fun Project.publishAndroidxReference(target: KotlinOnlyTarget<*>, newRootComponent: CustomRootComponent) {
internal fun Project.publishAndroidxReference(target: AbstractKotlinTarget, newRootComponent: CustomRootComponent) {
afterEvaluate {
extensions.getByType(PublishingExtension::class.java).apply {
val kotlinMultiplatform = publications
Expand Down Expand Up @@ -101,6 +101,7 @@ internal fun Project.publishAndroidxReference(target: KotlinOnlyTarget<*>, newRo
val usages = when (component) {
is KotlinVariant -> component.usages
is KotlinVariantWithMetadataVariant -> component.usages
is JointAndroidKotlinTargetComponent -> component.usages
else -> emptyList()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#
# Copyright 2024 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.
#

implementation-class=androidx.build.JetbrainsAndroidXPlugin
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ class AndroidXComposeImplPlugin : Plugin<Project> {

if (plugin is KotlinMultiplatformPluginWrapper) {
project.configureForMultiplatform()
enableArtifactRedirectingPublishing(project)
}
}
}
Expand Down
Loading

0 comments on commit 73be0df

Please sign in to comment.