Align kotlin stdlib jvm variants versions

Instead of doing substitution for stdlib, plugin now just aligns jvm
variants version in all dependencies to one. This eliminates issue when
JPMS module requires kotlin.stdlib.jdk8 (or jdk7) and fails to find it
in compilation classpath. Kotlin-stdlib-jdk8(,jdk7) variants in 1.8.0 is
 empty one, so new approach will not cause duplicate classes in
 classpath issue.

 Gradle property to disable alignment was renamed from "kotlin
 .stdlib.jdk.variants.substitution" to "kotlin.stdlib.jdk.variants
 .version.alignment" to better reflect new approach.

^KT-54703 Fixed
This commit is contained in:
Yahor Berdnikau
2022-10-31 22:45:30 +01:00
committed by Space Team
parent 0122b73bae
commit c7363adf10
20 changed files with 301 additions and 36 deletions
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.gradle
import org.gradle.api.JavaVersion
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.testbase.*
import org.junit.jupiter.api.DisplayName
@@ -14,9 +15,9 @@ import kotlin.io.path.appendText
class StdlibAlignmentIT : KGPBaseTest() {
@AndroidGradlePluginTests
@DisplayName("stdlib-jdk7, stdlib-jdk8 are substituted with stdlib:1.8+")
@DisplayName("stdlib-jdk7, stdlib-jdk8 versions aligned with stdlib:1.8+")
@GradleAndroidTest
fun testStdlibSubstitutionAndroid(
fun testStdlibAlignmentAndroid(
gradleVersion: GradleVersion,
agpVersion: String,
jdkVersion: JdkVersions.ProvidedJdk
@@ -45,9 +46,9 @@ class StdlibAlignmentIT : KGPBaseTest() {
}
@AndroidGradlePluginTests
@DisplayName("stdlib-jdk7, stdlib-jdk8 substitution with stdlib:1.8+ is possible to disable")
@DisplayName("alignment is possible to disable")
@GradleAndroidTest
fun testDisableStdlibSubstitutionAndroid(
fun testDisableStdlibAlignmentAndroid(
gradleVersion: GradleVersion,
agpVersion: String,
jdkVersion: JdkVersions.ProvidedJdk
@@ -74,7 +75,7 @@ class StdlibAlignmentIT : KGPBaseTest() {
gradleProperties.appendText(
"""
|
|kotlin.stdlib.jdk.variants.substitution=false
|kotlin.stdlib.jdk.variants.version.alignment=false
""".trimMargin()
)
@@ -85,9 +86,9 @@ class StdlibAlignmentIT : KGPBaseTest() {
}
@JvmGradlePluginTests
@DisplayName("substitution is working when stldib-jdk7:1.8+ is added as dependency")
@DisplayName("alignment is working when stdlib-jdk7:1.8+ is added as dependency")
@GradleTest
fun stdlibJdk7Substitution(gradleVersion: GradleVersion) {
fun stdlibJdk7Alignment(gradleVersion: GradleVersion) {
project("sourceSetsKotlinDsl", gradleVersion) {
buildGradleKts.appendText(
"""
@@ -104,19 +105,17 @@ class StdlibAlignmentIT : KGPBaseTest() {
"""
|\--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2
| \--- org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2
| +--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.30 -> org.jetbrains.kotlin:kotlin-stdlib:${buildOptions.kotlinVersion} (*)
| \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.5.30 -> ${buildOptions.kotlinVersion}
|
""".trimMargin()
| +--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.30 -> ${buildOptions.kotlinVersion}
""".trimMargin().normalizeLineEndings()
)
}
}
}
@JvmGradlePluginTests
@DisplayName("stdlib-jdk7, stdlib-jdk8 substitution with stdlib:1.8+ in Kotlin DSL")
@DisplayName("alignment in Kotlin DSL")
@GradleTest
fun stdlibJdkVariantsSubstitutionKotlinDsl(gradleVersion: GradleVersion) {
fun stdlibJdkAlignmentKotlinDsl(gradleVersion: GradleVersion) {
project("sourceSetsKotlinDsl", gradleVersion) {
buildGradleKts.appendText(
"""
@@ -133,19 +132,17 @@ class StdlibAlignmentIT : KGPBaseTest() {
"""
|\--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2
| \--- org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2
| +--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.30 -> org.jetbrains.kotlin:kotlin-stdlib:${buildOptions.kotlinVersion} (*)
| \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.5.30 -> ${buildOptions.kotlinVersion}
|
""".trimMargin()
| +--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.30 -> ${buildOptions.kotlinVersion}
""".trimMargin().normalizeLineEndings()
)
}
}
}
@JvmGradlePluginTests
@DisplayName("KT-54653: stdlib-jdk7, jdk8 substitution should not work for unrelated configuration")
@DisplayName("KT-54653: alignment should not work for unrelated configuration")
@GradleTest
fun stdlibJdkVariantsSubstitutionOnlyConfiguration(gradleVersion: GradleVersion) {
fun stdlibJdkAlignmentUnrelatedConfigurations(gradleVersion: GradleVersion) {
project("simpleProject", gradleVersion) {
buildGradle.modify {
"""
@@ -169,16 +166,16 @@ class StdlibAlignmentIT : KGPBaseTest() {
|\--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.20 -> org.jetbrains.kotlin:kotlin-stdlib:1.7.20
| +--- org.jetbrains.kotlin:kotlin-stdlib-common:1.7.20
| \--- org.jetbrains:annotations:13.0
""".trimMargin()
""".trimMargin().normalizeLineEndings()
)
}
}
}
@JvmGradlePluginTests
@DisplayName("KT-54653: stdlib-jdk7, jdk8 substitution works for complex resolvable configurations hierarchy")
@DisplayName("KT-54653: alignment works for complex resolvable configurations hierarchy")
@GradleTest
fun stdlibJdkVariantSubstitutionComplexResolvableConfiguration(gradleVersion: GradleVersion) {
fun stdlibJdkAlignmentComplexResolvableConfiguration(gradleVersion: GradleVersion) {
project("simpleProject", gradleVersion) {
buildGradle.modify {
"""
@@ -196,7 +193,7 @@ class StdlibAlignmentIT : KGPBaseTest() {
|
|dependencies {
| specificDeps "org.jetbrains.kotlin:kotlin-stdlib:${buildOptions.kotlinVersion}"
| // brings transitevly older stdlib-jdk7,8 dependencies
| // brings transitively older stdlib-jdk7,8 dependencies
| specificDepsChild "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2"
|}
""".trimMargin()
@@ -222,4 +219,50 @@ class StdlibAlignmentIT : KGPBaseTest() {
}
}
}
@JvmGradlePluginTests
@DisplayName("KT-54703: JPMS projects with dependency on kotlin.stdlib.jdk8 work as expected")
@JdkVersions(versions = [JavaVersion.VERSION_11])
@GradleTestVersions(minVersion = TestVersions.Gradle.G_7_0)
@GradleWithJdkTest
fun alignmentWorksCorrectlyForJPMS(
gradleVersion: GradleVersion,
providedJdk: JdkVersions.ProvidedJdk
) {
project(
"jpmsProject",
gradleVersion,
buildJdk = providedJdk.location
) {
subProject("list").buildGradleKts.appendText(
"""
|
|dependencies {
| api("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.20")
|}
""".trimMargin()
)
subProject("list").javaSourcesDir()
.resolve("module-info.java")
.modify {
val lines = it.lines()
"""
|${lines.first()}
| requires transitive kotlin.stdlib.jdk8;
|${lines.drop(1).joinToString(separator = System.lineSeparator())}
""".trimMargin()
}
subProject("application").buildGradleKts.appendText(
"""
|
|dependencies {
| implementation("org.jetbrains.kotlin:kotlin-stdlib")
|}
""".trimMargin()
)
build(":application:assemble")
}
}
}
@@ -18,3 +18,5 @@ internal fun BuildResult.printBuildOutput() {
""".trimMargin()
)
}
internal fun String.normalizeLineEndings(): String = replace("\n", System.lineSeparator())
@@ -0,0 +1,13 @@
plugins {
id("org.jetbrains.kotlin.jvm")
application
}
dependencies {
implementation(project(":utilities"))
}
application {
mainModule.set("org.gradle.sample.app")
mainClass.set("org.gradle.sample.app.Main")
}
@@ -0,0 +1,4 @@
module org.gradle.sample.app {
exports org.gradle.sample.app;
requires org.gradle.sample.utilities;
}
@@ -0,0 +1,15 @@
package org.gradle.sample.app;
import org.gradle.sample.list.LinkedList;
import static org.gradle.sample.utilities.StringUtils.join;
import static org.gradle.sample.utilities.StringUtils.split;
import static org.gradle.sample.app.MessageUtils.getMessage;
public class Main {
public static void main(String[] args) {
LinkedList tokens;
tokens = split(getMessage());
System.out.println(join(tokens));
}
}
@@ -0,0 +1,7 @@
package org.gradle.sample.app;
class MessageUtils {
public static String getMessage() {
return "Hello, World!";
}
}
@@ -0,0 +1,11 @@
package org.gradle.sample.app;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class MessageUtilsTest {
@Test public void testGetMessage() {
assertEquals("Hello, World!", MessageUtils.getMessage());
}
}
@@ -0,0 +1,6 @@
subprojects {
repositories {
mavenLocal()
mavenCentral()
}
}
@@ -0,0 +1,4 @@
plugins {
id("org.jetbrains.kotlin.jvm")
`java`
}
@@ -0,0 +1,3 @@
module org.gradle.sample.list {
exports org.gradle.sample.list;
}
@@ -0,0 +1,78 @@
package org.gradle.sample.list;
public class LinkedList {
private Node head;
public void add(String element) {
Node newNode = new Node(element);
Node it = tail(head);
if (it == null) {
head = newNode;
} else {
it.next = newNode;
}
}
private static Node tail(Node head) {
Node it;
for (it = head; it != null && it.next != null; it = it.next) {}
return it;
}
public boolean remove(String element) {
boolean result = false;
Node previousIt = null;
Node it = null;
for (it = head; !result && it != null; previousIt = it, it = it.next) {
if (0 == element.compareTo(it.data)) {
result = true;
unlink(previousIt, it);
break;
}
}
return result;
}
private void unlink(Node previousIt, Node currentIt) {
if (currentIt == head) {
head = currentIt.next;
} else {
previousIt.next = currentIt.next;
}
}
public int size() {
int size = 0;
for (Node it = head; it != null; ++size, it = it.next) {}
return size;
}
public String get(int index) {
Node it = head;
while (index > 0 && it != null) {
it = it.next;
index--;
}
if (it == null) {
throw new IndexOutOfBoundsException("Index is out of range");
}
return it.data;
}
private static class Node {
final String data;
Node next;
Node(String data) {
this.data = data;
}
}
}
@@ -0,0 +1,2 @@
rootProject.name = "modules-multi-project"
include("application", "list", "utilities")
@@ -0,0 +1,7 @@
plugins {
id("org.jetbrains.kotlin.jvm")
}
dependencies {
api(project(":list"))
}
@@ -0,0 +1,4 @@
module org.gradle.sample.utilities {
requires transitive org.gradle.sample.list;
exports org.gradle.sample.utilities;
}
@@ -0,0 +1,17 @@
package org.gradle.sample.utilities;
import org.gradle.sample.list.LinkedList;
class JoinUtils {
public static String join(LinkedList source) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < source.size(); ++i) {
if (result.length() > 0) {
result.append(" ");
}
result.append(source.get(i));
}
return result.toString();
}
}
@@ -0,0 +1,36 @@
package org.gradle.sample.utilities;
import org.gradle.sample.list.LinkedList;
class SplitUtils {
public static LinkedList split(String source) {
int lastFind = 0;
int currentFind = 0;
LinkedList result = new LinkedList();
while ((currentFind = source.indexOf(" ", lastFind)) != -1) {
String token = source.substring(lastFind);
if (currentFind != -1) {
token = token.substring(0, currentFind - lastFind);
}
addIfValid(token, result);
lastFind = currentFind + 1;
}
String token = source.substring(lastFind);
addIfValid(token, result);
return result;
}
private static void addIfValid(String token, LinkedList list) {
if (isTokenValid(token)) {
list.add(token);
}
}
private static boolean isTokenValid(String token) {
return !token.isEmpty();
}
}
@@ -0,0 +1,13 @@
package org.gradle.sample.utilities;
import org.gradle.sample.list.LinkedList;
public class StringUtils {
public static String join(LinkedList source) {
return JoinUtils.join(source);
}
public static LinkedList split(String source) {
return SplitUtils.split(source);
}
}
@@ -47,8 +47,8 @@ internal fun customizeKotlinDependencies(project: Project) {
coreLibrariesVersion
)
if (propertiesProvider.stdlibJdkVariantsSubstitution) {
project.configurations.configureStdlibSubstitution()
if (propertiesProvider.stdlibJdkVariantsVersionAlignment) {
project.configurations.configureStdlibVersionAlignment()
}
excludeStdlibAndKotlinTestCommonFromPlatformCompilations(project)
@@ -51,10 +51,10 @@ internal fun Project.configureStdlibDefaultDependency(
}
/**
* Replacing kotlin-stdlib-jdk8 and kotlin-stdlib-jdk7 artifacts with kotlin-stdlib
* Aligning kotlin-stdlib-jdk8 and kotlin-stdlib-jdk7 dependencies versions with kotlin-stdlib (or kotlin-stdlib-jdk7)
* when project stdlib version is >= 1.8.0
*/
internal fun ConfigurationContainer.configureStdlibSubstitution() = all { configuration ->
internal fun ConfigurationContainer.configureStdlibVersionAlignment() = all { configuration ->
configuration.withDependencies { dependencySet ->
dependencySet
.withType<ExternalDependency>()
@@ -64,32 +64,32 @@ internal fun ConfigurationContainer.configureStdlibSubstitution() = all { config
dependency.version != null &&
SemVer.from(dependency.version!!) >= kotlin180Version
) {
if (configuration.isCanBeResolved) configuration.substitudeStdlibJvmVariants(dependency)
if (configuration.isCanBeResolved) configuration.alignStdlibJvmVariantVersions(dependency)
// dependency substitution only works for resolvable configuration,
// so we need to find all configuration that extends current one
filter {
it.isCanBeResolved && it.hierarchy.contains(configuration)
}.forEach {
it.substitudeStdlibJvmVariants(dependency)
it.alignStdlibJvmVariantVersions(dependency)
}
}
}
}
}
private fun Configuration.substitudeStdlibJvmVariants(
private fun Configuration.alignStdlibJvmVariantVersions(
kotlinStdlibDependency: ExternalDependency
) {
resolutionStrategy.dependencySubstitution {
if (kotlinStdlibDependency.name != "kotlin-stdlib-jdk7") {
it.substitute(it.module("org.jetbrains.kotlin:kotlin-stdlib-jdk7"))
.using(it.module("org.jetbrains.kotlin:kotlin-stdlib:${kotlinStdlibDependency.version}"))
.using(it.module("org.jetbrains.kotlin:kotlin-stdlib-jdk7:${kotlinStdlibDependency.version}"))
.because("kotlin-stdlib-jdk7 is now part of kotlin-stdlib")
}
it.substitute(it.module("org.jetbrains.kotlin:kotlin-stdlib-jdk8"))
.using(it.module("org.jetbrains.kotlin:kotlin-stdlib:${kotlinStdlibDependency.version}"))
.using(it.module("org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlinStdlibDependency.version}"))
.because("kotlin-stdlib-jdk8 is now part of kotlin-stdlib")
}
}
@@ -24,7 +24,7 @@ import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLI
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_HIERARCHICAL_STRUCTURE_SUPPORT
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_NATIVE_DEPENDENCY_PROPAGATION
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_STDLIB_DEFAULT_DEPENDENCY
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_STDLIB_JDK_VARIANTS_SUBSTITUTION
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_STDLIB_JDK_VARIANTS_VERSION_ALIGNMENT
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinIrJsGeneratedTSValidationStrategy
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrOutputGranularity
@@ -403,8 +403,8 @@ internal class PropertiesProvider private constructor(private val project: Proje
val stdlibDefaultDependency: Boolean
get() = booleanProperty(KOTLIN_STDLIB_DEFAULT_DEPENDENCY) ?: true
val stdlibJdkVariantsSubstitution: Boolean
get() = booleanProperty(KOTLIN_STDLIB_JDK_VARIANTS_SUBSTITUTION) ?: true
val stdlibJdkVariantsVersionAlignment: Boolean
get() = booleanProperty(KOTLIN_STDLIB_JDK_VARIANTS_VERSION_ALIGNMENT) ?: true
val kotlinTestInferJvmVariant: Boolean
get() = booleanProperty("kotlin.test.infer.jvm.variant") ?: true
@@ -478,7 +478,7 @@ internal class PropertiesProvider private constructor(private val project: Proje
object PropertyNames {
const val KOTLIN_STDLIB_DEFAULT_DEPENDENCY = "kotlin.stdlib.default.dependency"
const val KOTLIN_STDLIB_JDK_VARIANTS_SUBSTITUTION = "kotlin.stdlib.jdk.variants.substitution"
const val KOTLIN_STDLIB_JDK_VARIANTS_VERSION_ALIGNMENT = "kotlin.stdlib.jdk.variants.version.alignment"
const val KOTLIN_MPP_ENABLE_GRANULAR_SOURCE_SETS_METADATA = "kotlin.mpp.enableGranularSourceSetsMetadata"
const val KOTLIN_MPP_ENABLE_CINTEROP_COMMONIZATION = "kotlin.mpp.enableCInteropCommonization"
const val KOTLIN_MPP_HIERARCHICAL_STRUCTURE_BY_DEFAULT = "kotlin.internal.mpp.hierarchicalStructureByDefault"