Last active
April 28, 2025 14:00
-
-
Save abdulowork/b6d29701dc57568459905c682218739d to your computer and use it in GitHub Desktop.
KGP transform graph explosion
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.gradle.api.attributes.Usage.* | |
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType | |
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet | |
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget | |
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinUsages.KOTLIN_API | |
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinUsages.KOTLIN_METADATA | |
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinUsages.KOTLIN_RUNTIME | |
plugins { | |
kotlin("multiplatform") version "2.1.20" | |
id("com.android.library") version "8.7.0" | |
} | |
group = "org.example" | |
version = "1.0" | |
kotlin { | |
iosArm64() | |
iosX64() | |
iosSimulatorArm64() | |
watchosArm32() | |
watchosArm64() | |
watchosSimulatorArm64() | |
tvosArm64() | |
tvosX64() | |
tvosSimulatorArm64() | |
tvosSimulatorArm64() | |
linuxArm64() | |
linuxX64() | |
macosX64() | |
macosArm64() | |
js() | |
wasmJs() | |
wasmWasi() | |
jvm() | |
sourceSets.commonMain.dependencies { | |
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0") | |
} | |
} | |
android { | |
compileSdk = 34 | |
defaultConfig { | |
minSdk = 31 | |
} | |
namespace = "org.jetbrains.kotlin.sample" | |
} | |
@CacheableTransform | |
internal abstract class UnzipUklibTransform @Inject constructor( | |
private val fileOperations: FileSystemOperations, | |
private val archiveOperations: ArchiveOperations, | |
) : TransformAction<TransformParameters.None> { | |
@get:PathSensitive(PathSensitivity.RELATIVE) | |
@get:InputArtifact | |
abstract val inputArtifact: Provider<FileSystemLocation> | |
override fun transform(outputs: TransformOutputs) { | |
outputs.file(inputArtifact.get().asFile) | |
} | |
} | |
@CacheableTransform | |
internal abstract class RemoveMetadataJarsTransform : TransformAction<TransformParameters.None> { | |
@get:PathSensitive(PathSensitivity.RELATIVE) | |
@get:InputArtifact | |
abstract val inputArtifact: Provider<FileSystemLocation> | |
override fun transform(outputs: TransformOutputs) { | |
outputs.file(inputArtifact.get().asFile) | |
} | |
} | |
@CacheableTransform | |
internal abstract class UnzippedUklibToPlatformCompilationTransform : | |
TransformAction<UnzippedUklibToPlatformCompilationTransform.Parameters> { | |
interface Parameters : TransformParameters { | |
@get:Input | |
val targetFragmentAttribute: Property<String> | |
} | |
@get:PathSensitive(PathSensitivity.RELATIVE) | |
@get:InputArtifact | |
abstract val inputArtifact: Provider<FileSystemLocation> | |
override fun transform(outputs: TransformOutputs) { | |
outputs.dir(inputArtifact.get().asFile) | |
} | |
} | |
internal val uklibStateAttribute = Attribute.of("org.jetbrains.kotlin.uklibState", String::class.java) | |
internal val uklibStateCompressed = "compressed" | |
internal val uklibStateDecompressed = "decompressed" | |
internal val uklibViewAttribute = Attribute.of("org.jetbrains.kotlin.uklibView", String::class.java) | |
internal val uklibViewAttributeWholeUklib = "whole_uklib" | |
internal val isUklib = Attribute.of("org.jetbrains.kotlin.uklib", String::class.java) | |
internal val isUklibTrue = "true" | |
private val isMetadataJar = Attribute.of("org.jetbrains.kotlin.isMetadataJar", String::class.java) | |
private val isMetadataJarUnknown = "unknown" | |
private val notMetadataJar = "not-a-metadata-jar" | |
setupUklibConsumption() | |
private fun Project.setupUklibConsumption() { | |
val sourceSets = kotlin.sourceSets | |
val targets = kotlin.targets | |
registerCompressedUklibArtifact() | |
allowUklibsToDecompress() | |
allowMetadataConfigurationsToResolveUnzippedUklib(sourceSets) | |
allowPSMBasedKMPToResolveLenientlyAndSelectBestMatchingVariant() | |
allowPlatformCompilationsToResolvePlatformCompilationArtifactFromUklib(targets) | |
} | |
private fun Project.allowPlatformCompilationsToResolvePlatformCompilationArtifactFromUklib( | |
targets: NamedDomainObjectCollection<KotlinTarget> | |
) { | |
targets.configureEach { | |
val target = this | |
dependencies.registerTransform(UnzippedUklibToPlatformCompilationTransform::class.java) { | |
with(from) { | |
attribute(uklibStateAttribute, uklibStateDecompressed) | |
attribute(uklibViewAttribute, uklibViewAttributeWholeUklib) | |
} | |
with(to) { | |
attribute(uklibStateAttribute, uklibStateDecompressed) | |
attribute(uklibViewAttribute, target.name) | |
} | |
parameters.targetFragmentAttribute.set(target.name) | |
} | |
target.compilations.configureEach { | |
val compilation = this | |
listOfNotNull<Pair<Configuration, Usage>>( | |
configurations.getByName(compilation.compileDependencyConfigurationName) to usageByName(UklibUsages.KOTLIN_UKLIB_API), | |
compilation.runtimeDependencyConfigurationName?.let { configurations.getByName(it) to usageByName(UklibUsages.KOTLIN_UKLIB_RUNTIME)}, | |
).forEach { | |
it.first.applyUklibAttributes(it.second, target.name) | |
} | |
} | |
} | |
} | |
private fun Configuration.applyUklibAttributes( | |
usage: Usage, | |
uklibFragmentPlatformAttribute: String, | |
) { | |
with(attributes) { | |
attribute(USAGE_ATTRIBUTE, usage) | |
attribute(uklibStateAttribute, uklibStateDecompressed) | |
attribute(uklibViewAttribute, uklibFragmentPlatformAttribute) | |
attribute( | |
isMetadataJar, | |
notMetadataJar | |
) | |
attribute( | |
isUklib, | |
isUklibTrue | |
) | |
} | |
} | |
private fun Project.registerCompressedUklibArtifact() { | |
with(dependencies.artifactTypes.create("uklib").attributes) { | |
attribute(uklibStateAttribute, uklibStateCompressed) | |
attribute(uklibViewAttribute, uklibViewAttributeWholeUklib) | |
} | |
} | |
private fun Project.allowUklibsToDecompress() { | |
dependencies.registerTransform(UnzipUklibTransform::class.java) { | |
from.attribute(uklibStateAttribute, uklibStateCompressed) | |
to.attribute(uklibStateAttribute, uklibStateDecompressed) | |
} | |
} | |
private fun Project.allowMetadataConfigurationsToResolveUnzippedUklib( | |
sourceSets: NamedDomainObjectContainer<KotlinSourceSet>, | |
) { | |
sourceSets.all { | |
val ss = this | |
afterEvaluate { | |
afterEvaluate { | |
configurations.getByName("${ss.name}ResolvableDependenciesMetadata").attributes { | |
attribute(USAGE_ATTRIBUTE, usageByName(UklibUsages.KOTLIN_UKLIB_METADATA)) | |
attribute(uklibStateAttribute, uklibStateDecompressed) | |
attribute(uklibViewAttribute, uklibViewAttributeWholeUklib) | |
attribute( | |
isUklib, | |
isUklibTrue | |
) | |
} | |
} | |
} | |
} | |
} | |
private fun Project.allowPSMBasedKMPToResolveLenientlyAndSelectBestMatchingVariant() { | |
dependencies.attributesSchema.attribute(USAGE_ATTRIBUTE) { | |
val strategy = this | |
strategy.compatibilityRules.add(AllowPlatformConfigurationsToFallBackToMetadataForLenientKmpResolutionUsage::class.java) | |
strategy.disambiguationRules.add(SelectBestMatchingVariantForKmpResolutionUsage::class.java) | |
} | |
dependencies.attributesSchema.attribute(KotlinPlatformType.attribute) { | |
val strategy = this | |
strategy.compatibilityRules.add(AllowPlatformConfigurationsToFallBackToMetadataForLenientKmpResolution::class.java) | |
} | |
with(dependencies.artifactTypes.getByName("jar").attributes) { | |
attribute( | |
isMetadataJar, | |
isMetadataJarUnknown | |
) | |
} | |
dependencies.registerTransform(RemoveMetadataJarsTransform::class.java) { | |
from.attribute( | |
isMetadataJar, | |
isMetadataJarUnknown | |
) | |
to.attribute( | |
isMetadataJar, | |
notMetadataJar | |
) | |
} | |
} | |
private class AllowPlatformConfigurationsToFallBackToMetadataForLenientKmpResolution : AttributeCompatibilityRule<KotlinPlatformType> { | |
override fun execute(details: CompatibilityCheckDetails<KotlinPlatformType>) = with(details) { | |
consumerValue?.name ?: return@with | |
val producer = producerValue?.name ?: return@with | |
if (producer == KotlinPlatformType.common.name) compatible() | |
} | |
} | |
internal class AllowPlatformConfigurationsToFallBackToMetadataForLenientKmpResolutionUsage : AttributeCompatibilityRule<Usage> { | |
override fun execute(details: CompatibilityCheckDetails<Usage>) = with(details) { | |
val consumerUsage = consumerValue?.name ?: return@with | |
val producerUsage = producerValue?.name ?: return@with | |
if ( | |
mapOf( | |
UklibUsages.KOTLIN_UKLIB_API to setOf( | |
KOTLIN_API, | |
JAVA_API, | |
KOTLIN_METADATA | |
), | |
UklibUsages.KOTLIN_UKLIB_RUNTIME to setOf( | |
KOTLIN_RUNTIME, | |
JAVA_RUNTIME, | |
JAVA_API, | |
), | |
UklibUsages.KOTLIN_UKLIB_METADATA to setOf( | |
UklibUsages.KOTLIN_UKLIB_API, | |
KOTLIN_METADATA, | |
KOTLIN_API, | |
JAVA_API, | |
), | |
)[consumerUsage]?.contains(producerUsage) == true | |
) compatible() | |
} | |
} | |
internal class SelectBestMatchingVariantForKmpResolutionUsage : AttributeDisambiguationRule<Usage> { | |
override fun execute(details: MultipleCandidatesDetails<Usage>) = details.run { | |
val consumerUsage = consumerValue?.name ?: return@run | |
details.candidateValues | |
mapOf( | |
UklibUsages.KOTLIN_UKLIB_API to listOf( | |
UklibUsages.KOTLIN_UKLIB_API, | |
KOTLIN_API, | |
JAVA_API, | |
KOTLIN_METADATA, | |
), | |
UklibUsages.KOTLIN_UKLIB_RUNTIME to listOf( | |
UklibUsages.KOTLIN_UKLIB_RUNTIME, | |
KOTLIN_RUNTIME, | |
JAVA_RUNTIME, | |
JAVA_API, | |
KOTLIN_METADATA | |
), | |
UklibUsages.KOTLIN_UKLIB_METADATA to listOf( | |
UklibUsages.KOTLIN_UKLIB_API, | |
KOTLIN_METADATA, | |
), | |
)[consumerUsage]?.let { | |
closestMatchToFirstAppropriateCandidate(it) | |
} | |
return@run | |
} | |
private fun MultipleCandidatesDetails<Usage>.closestMatchToFirstAppropriateCandidate(acceptedProducerValues: List<String>) { | |
val candidatesMap = candidateValues.associateBy { it.name } | |
acceptedProducerValues.firstOrNull { it in candidatesMap }?.let { closestMatch(candidatesMap.getValue(it)) } | |
} | |
} | |
private fun usageByName(name: String): Usage { | |
return objects.named<Usage>(name) | |
} | |
object UklibUsages { | |
const val KOTLIN_UKLIB_API = "KOTLIN_UKLIB_API" | |
const val KOTLIN_UKLIB_RUNTIME = "KOTLIN_UKLIB_RUNTIME" | |
const val KOTLIN_UKLIB_METADATA = "KOTLIN_UKLIB_METADATA" | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
dependencyResolutionManagement { | |
repositories { | |
mavenCentral() | |
google() | |
} | |
} | |
pluginManagement { | |
repositories { | |
mavenCentral() | |
google() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment