From ba2c98f7fdd5886fbc79a352f323692813fb39f5 Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Sat, 16 Apr 2022 18:39:11 +0100 Subject: [PATCH] Run the kotlin metadata remapper in its own classloader with the compiler kotlin version. (#626) --- .../loom/configuration/mods/ModProcessor.java | 17 +++- .../task/service/TinyRemapperService.java | 26 +++-- .../loom/util/kotlin/KotlinClasspath.java | 34 +++++++ .../util/kotlin/KotlinClasspathService.java | 73 ++++++++++++++ .../KotlinMetadataTinyRemapperExtension.java | 30 ++++++ .../loom/util/kotlin/KotlinPluginUtils.java | 52 ++++++++++ .../kotlin/KotlinRemapperClassloader.java | 93 ++++++++++++++++++ ...ClassMetadataRemappingAnnotationVisitor.kt | 12 +-- .../KotlinMetadataRemappingClassVisitor.kt | 6 ++ ...otlinMetadataTinyRemapperExtensionImpl.kt} | 5 +- .../loom/test/LoomTestConstants.groovy | 2 +- .../KotlinRemapperClassloaderTest.groovy | 96 +++++++++++++++++++ .../projects/kotlin/build.gradle.kts | 6 +- .../fabricmc/language/kotlin/TestExtension.kt | 5 + .../fabricmc/language/kotlin/TestModClass.kt | 4 +- 15 files changed, 435 insertions(+), 26 deletions(-) create mode 100644 src/main/java/net/fabricmc/loom/util/kotlin/KotlinClasspath.java create mode 100644 src/main/java/net/fabricmc/loom/util/kotlin/KotlinClasspathService.java create mode 100644 src/main/java/net/fabricmc/loom/util/kotlin/KotlinMetadataTinyRemapperExtension.java create mode 100644 src/main/java/net/fabricmc/loom/util/kotlin/KotlinPluginUtils.java create mode 100644 src/main/java/net/fabricmc/loom/util/kotlin/KotlinRemapperClassloader.java rename src/main/kotlin/net/fabricmc/loom/kotlin/remapping/{KotlinMetadataTinyRemapperExtension.kt => KotlinMetadataTinyRemapperExtensionImpl.kt} (89%) create mode 100644 src/test/groovy/net/fabricmc/loom/test/unit/kotlin/KotlinRemapperClassloaderTest.groovy diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java b/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java index 872fb6fa..0f982155 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java @@ -49,11 +49,12 @@ import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.configuration.RemappedConfigurationEntry; import net.fabricmc.loom.configuration.processors.dependency.ModDependencyInfo; import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; -import net.fabricmc.loom.kotlin.remapping.KotlinMetadataTinyRemapperExtension; import net.fabricmc.loom.task.RemapJarTask; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.TinyRemapperHelper; import net.fabricmc.loom.util.ZipUtils; +import net.fabricmc.loom.util.kotlin.KotlinClasspathService; +import net.fabricmc.loom.util.kotlin.KotlinRemapperClassloader; import net.fabricmc.tinyremapper.InputTag; import net.fabricmc.tinyremapper.NonClassCopyMode; import net.fabricmc.tinyremapper.OutputConsumerPath; @@ -139,8 +140,6 @@ public class ModProcessor { private void remapJars(List remapList) throws IOException { final LoomGradleExtension extension = LoomGradleExtension.get(project); final MappingsProviderImpl mappingsProvider = extension.getMappingsProvider(); - final boolean useKotlinExtension = project.getPluginManager().hasPlugin("org.jetbrains.kotlin.jvm"); - Path[] mcDeps = project.getConfigurations().getByName(Constants.Configurations.LOADER_DEPENDENCIES).getFiles() .stream().map(File::toPath).toArray(Path[]::new); @@ -150,8 +149,12 @@ public class ModProcessor { .withMappings(TinyRemapperHelper.create(mappingsProvider.getMappings(), fromM, toM, false)) .renameInvalidLocals(false); - if (useKotlinExtension) { - builder.extension(KotlinMetadataTinyRemapperExtension.INSTANCE); + final KotlinClasspathService kotlinClasspathService = KotlinClasspathService.getOrCreateIfRequired(project); + KotlinRemapperClassloader kotlinRemapperClassloader = null; + + if (kotlinClasspathService != null) { + kotlinRemapperClassloader = KotlinRemapperClassloader.create(kotlinClasspathService); + builder.extension(kotlinRemapperClassloader.getTinyRemapperExtension()); } final TinyRemapper remapper = builder.build(); @@ -209,6 +212,10 @@ public class ModProcessor { } } finally { remapper.finish(); + + if (kotlinRemapperClassloader != null) { + kotlinRemapperClassloader.close(); + } } for (ModDependencyInfo info : remapList) { diff --git a/src/main/java/net/fabricmc/loom/task/service/TinyRemapperService.java b/src/main/java/net/fabricmc/loom/task/service/TinyRemapperService.java index 54d974a6..78f146bb 100644 --- a/src/main/java/net/fabricmc/loom/task/service/TinyRemapperService.java +++ b/src/main/java/net/fabricmc/loom/task/service/TinyRemapperService.java @@ -36,10 +36,13 @@ import java.util.Objects; import java.util.StringJoiner; import org.gradle.api.Project; +import org.jetbrains.annotations.Nullable; import net.fabricmc.loom.LoomGradleExtension; -import net.fabricmc.loom.kotlin.remapping.KotlinMetadataTinyRemapperExtension; import net.fabricmc.loom.task.AbstractRemapJarTask; +import net.fabricmc.loom.util.kotlin.KotlinClasspath; +import net.fabricmc.loom.util.kotlin.KotlinClasspathService; +import net.fabricmc.loom.util.kotlin.KotlinRemapperClassloader; import net.fabricmc.loom.util.service.SharedService; import net.fabricmc.loom.util.service.SharedServiceManager; import net.fabricmc.tinyremapper.IMappingProvider; @@ -54,15 +57,15 @@ public class TinyRemapperService implements SharedService { final LoomGradleExtension extension = LoomGradleExtension.get(project); final SharedServiceManager sharedServiceManager = SharedServiceManager.get(project); final boolean legacyMixin = extension.getMixin().getUseLegacyMixinAp().get(); - final boolean useKotlinExtension = project.getPluginManager().hasPlugin("org.jetbrains.kotlin.jvm"); + final @Nullable KotlinClasspathService kotlinClasspathService = KotlinClasspathService.getOrCreateIfRequired(project); // Generates an id that is used to share the remapper across projects. This tasks in the remap jar task name to handle custom remap jar tasks separately. final var joiner = new StringJoiner(":"); joiner.add(extension.getMappingsProvider().getBuildServiceName("remapJarService", from, to)); joiner.add(remapJarTask.getName()); - if (useKotlinExtension) { - joiner.add("kotlin"); + if (kotlinClasspathService != null) { + joiner.add("kotlin-" + kotlinClasspathService.version()); } if (remapJarTask.getRemapperIsolation().get()) { @@ -79,7 +82,7 @@ public class TinyRemapperService implements SharedService { mappings.add(MixinMappingsService.getService(SharedServiceManager.get(project)).getMappingProvider(from, to)); } - return new TinyRemapperService(mappings, !legacyMixin, useKotlinExtension); + return new TinyRemapperService(mappings, !legacyMixin, kotlinClasspathService); }); service.readClasspath(remapJarTask.getClasspath().getFiles().stream().map(File::toPath).toList()); @@ -88,12 +91,14 @@ public class TinyRemapperService implements SharedService { } private TinyRemapper tinyRemapper; + @Nullable + private KotlinRemapperClassloader kotlinRemapperClassloader; private final Map inputTagMap = new HashMap<>(); private final HashSet classpath = new HashSet<>(); // Set to true once remapping has started, once set no inputs can be read. private boolean isRemapping = false; - public TinyRemapperService(List mappings, boolean useMixinExtension, boolean useKotlinExtension) { + public TinyRemapperService(List mappings, boolean useMixinExtension, @Nullable KotlinClasspath kotlinClasspath) { TinyRemapper.Builder builder = TinyRemapper.newRemapper(); for (IMappingProvider provider : mappings) { @@ -104,8 +109,9 @@ public class TinyRemapperService implements SharedService { builder.extension(new net.fabricmc.tinyremapper.extension.mixin.MixinExtension()); } - if (useKotlinExtension) { - builder.extension(KotlinMetadataTinyRemapperExtension.INSTANCE); + if (kotlinClasspath != null) { + kotlinRemapperClassloader = KotlinRemapperClassloader.create(kotlinClasspath); + builder.extension(kotlinRemapperClassloader.getTinyRemapperExtension()); } tinyRemapper = builder.build(); @@ -156,5 +162,9 @@ public class TinyRemapperService implements SharedService { tinyRemapper.finish(); tinyRemapper = null; } + + if (kotlinRemapperClassloader != null) { + kotlinRemapperClassloader.close(); + } } } diff --git a/src/main/java/net/fabricmc/loom/util/kotlin/KotlinClasspath.java b/src/main/java/net/fabricmc/loom/util/kotlin/KotlinClasspath.java new file mode 100644 index 00000000..f7461f08 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/kotlin/KotlinClasspath.java @@ -0,0 +1,34 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2022 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.util.kotlin; + +import java.net.URL; +import java.util.Set; + +public interface KotlinClasspath { + String version(); + + Set classpath(); +} diff --git a/src/main/java/net/fabricmc/loom/util/kotlin/KotlinClasspathService.java b/src/main/java/net/fabricmc/loom/util/kotlin/KotlinClasspathService.java new file mode 100644 index 00000000..0084fd47 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/kotlin/KotlinClasspathService.java @@ -0,0 +1,73 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2022 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.util.kotlin; + +import java.io.UncheckedIOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Set; +import java.util.stream.Collectors; + +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.jetbrains.annotations.Nullable; + +import net.fabricmc.loom.util.service.SharedService; +import net.fabricmc.loom.util.service.SharedServiceManager; + +public record KotlinClasspathService(Set classpath, String version) implements KotlinClasspath, SharedService { + @Nullable + public static KotlinClasspathService getOrCreateIfRequired(Project project) { + if (!KotlinPluginUtils.hasKotlinPlugin(project)) { + return null; + } + + return getOrCreate(project, KotlinPluginUtils.getKotlinPluginVersion(project)); + } + + public static synchronized KotlinClasspathService getOrCreate(Project project, String kotlinVersion) { + final String id = "kotlinclasspath:" + kotlinVersion; + final SharedServiceManager sharedServiceManager = SharedServiceManager.get(project); + return sharedServiceManager.getOrCreateService(id, () -> create(project, kotlinVersion)); + } + + private static KotlinClasspathService create(Project project, String kotlinVersion) { + // Create a detached config to resolve the koltin std lib for the provided version. + Configuration detachedConfiguration = project.getConfigurations().detachedConfiguration( + project.getDependencies().create("org.jetbrains.kotlin:kotlin-stdlib:" + kotlinVersion) + ); + + Set classpath = detachedConfiguration.getFiles().stream() + .map(file -> { + try { + return file.toURI().toURL(); + } catch (MalformedURLException e) { + throw new UncheckedIOException(e); + } + }).collect(Collectors.toSet());; + + return new KotlinClasspathService(classpath, kotlinVersion); + } +} diff --git a/src/main/java/net/fabricmc/loom/util/kotlin/KotlinMetadataTinyRemapperExtension.java b/src/main/java/net/fabricmc/loom/util/kotlin/KotlinMetadataTinyRemapperExtension.java new file mode 100644 index 00000000..1e13a350 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/kotlin/KotlinMetadataTinyRemapperExtension.java @@ -0,0 +1,30 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2022 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.util.kotlin; + +import net.fabricmc.tinyremapper.TinyRemapper; + +public interface KotlinMetadataTinyRemapperExtension extends TinyRemapper.ApplyVisitorProvider, TinyRemapper.Extension { +} diff --git a/src/main/java/net/fabricmc/loom/util/kotlin/KotlinPluginUtils.java b/src/main/java/net/fabricmc/loom/util/kotlin/KotlinPluginUtils.java new file mode 100644 index 00000000..0eb3a464 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/kotlin/KotlinPluginUtils.java @@ -0,0 +1,52 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2022 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.util.kotlin; + +import org.gradle.api.Project; +import org.gradle.api.artifacts.Dependency; +import org.gradle.api.artifacts.DependencySet; + +public class KotlinPluginUtils { + private static final String KOTLIN_PLUGIN_ID = "org.jetbrains.kotlin.jvm"; + private static final String KOTLIN_PLUGIN_GROUP = "org.jetbrains.kotlin.jvm"; + private static final String KOTLIN_PLUGIN_NAME = "org.jetbrains.kotlin.jvm.gradle.plugin"; + + public static boolean hasKotlinPlugin(Project project) { + return project.getPluginManager().hasPlugin(KOTLIN_PLUGIN_ID); + } + + public static String getKotlinPluginVersion(Project project) { + DependencySet buildDependencies = project.getBuildscript().getConfigurations() + .getByName("classpath").getDependencies(); + + for (Dependency dependency : buildDependencies) { + if (KOTLIN_PLUGIN_GROUP.equals(dependency.getGroup()) && KOTLIN_PLUGIN_NAME.equals(dependency.getName())) { + return dependency.getVersion(); + } + } + + throw new IllegalStateException("Unable to get the kotlin plugin version"); + } +} diff --git a/src/main/java/net/fabricmc/loom/util/kotlin/KotlinRemapperClassloader.java b/src/main/java/net/fabricmc/loom/util/kotlin/KotlinRemapperClassloader.java new file mode 100644 index 00000000..1ad422c4 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/kotlin/KotlinRemapperClassloader.java @@ -0,0 +1,93 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2022 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.util.kotlin; + +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import kotlinx.metadata.jvm.KotlinClassHeader; + +import net.fabricmc.loom.LoomGradlePlugin; +import net.fabricmc.loom.kotlin.remapping.KotlinMetadataTinyRemapperExtensionImpl; + +/** + * Used to run the Kotlin remapper with a specific version of Koltin that may not match the kotlin version included with gradle. + */ +public class KotlinRemapperClassloader extends URLClassLoader { + // Packages that should be loaded from the gradle plugin classloader. + private static final List PARENT_PACKAGES = List.of( + "net.fabricmc.tinyremapper", + "net.fabricmc.loom.util.kotlin", + "org.objectweb.asm", + "org.slf4j" + ); + + private KotlinRemapperClassloader(URL[] urls) { + super(urls, null); + } + + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + if (PARENT_PACKAGES.stream().anyMatch(name::startsWith)) { + return LoomGradlePlugin.class.getClassLoader().loadClass(name); + } + + return super.loadClass(name, resolve); + } + + public static KotlinRemapperClassloader create(KotlinClasspath classpathProvider) { + // Include the libraries that are not on the kotlin classpath. + final Stream loomUrls = getClassUrls( + KotlinMetadataTinyRemapperExtensionImpl.class, // Loom + KotlinClassHeader.class // Kotlin metadata api + ); + + final URL[] urls = Stream.concat( + loomUrls, + classpathProvider.classpath().stream() + ).toArray(URL[]::new); + + return new KotlinRemapperClassloader(urls); + } + + private static Stream getClassUrls(Class... classes) { + return Arrays.stream(classes).map(klass -> klass.getProtectionDomain().getCodeSource().getLocation()); + } + + /** + * Load the {@link KotlinMetadataTinyRemapperExtensionImpl} class on the new classloader. + */ + public KotlinMetadataTinyRemapperExtension getTinyRemapperExtension() { + try { + Class klass = this.loadClass(KotlinMetadataTinyRemapperExtensionImpl.class.getCanonicalName()); + return (KotlinMetadataTinyRemapperExtension) klass.getField("INSTANCE").get(null); + } catch (ClassNotFoundException | IllegalAccessException | NoSuchFieldException e) { + throw new RuntimeException("Failed to create instance", e); + } + } +} diff --git a/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinClassMetadataRemappingAnnotationVisitor.kt b/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinClassMetadataRemappingAnnotationVisitor.kt index 353a8b11..57fca33b 100644 --- a/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinClassMetadataRemappingAnnotationVisitor.kt +++ b/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinClassMetadataRemappingAnnotationVisitor.kt @@ -49,11 +49,11 @@ class KotlinClassMetadataRemappingAnnotationVisitor(private val remapper: Remapp val header = readHeader() ?: return - val headerVersion = header.metadataVersion.joinToString(".") - if (headerVersion != KotlinVersion.CURRENT.toString()) { - logger.warn("Skipping remap of kotlin metadata for class ($className) as it was built with Kotlin ($headerVersion) while Gradle is running with the included Kotlin version (${KotlinVersion.CURRENT}).") - accept(next) - return + val headerVersion = KotlinVersion(header.metadataVersion[0], header.metadataVersion[1], 0) + val currentMinorVersion = KotlinVersion(KotlinVersion.CURRENT.major, KotlinVersion.CURRENT.minor, 0) + + if (headerVersion != currentMinorVersion) { + logger.info("Kotlin metadata for class ($className) as it was built using a different major Kotlin version (${header.metadataVersion[0]}.${header.metadataVersion[1]}.x) while the remapper is using (${KotlinVersion.CURRENT}).") } when (val metadata = KotlinClassMetadata.read(header)) { @@ -154,7 +154,7 @@ class KotlinClassMetadataRemappingAnnotationVisitor(private val remapper: Remapp private fun validateKotlinClassHeader(remapped: KotlinClassHeader, original: KotlinClassHeader) { // This can happen when the remapper is ran on a kotlin version that does not match the version the class was compiled with. if (remapped.data2.size != original.data2.size) { - throw RuntimeException("Kotlin class metadata size mismatch: data2 size does not match original. New: ${remapped.data2.size} Old: ${original.data2.size}") + logger.info("Kotlin class metadata size mismatch: data2 size does not match original in class $className. New: ${remapped.data2.size} Old: ${original.data2.size}") } } } diff --git a/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinMetadataRemappingClassVisitor.kt b/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinMetadataRemappingClassVisitor.kt index 9a014555..e22b05fb 100644 --- a/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinMetadataRemappingClassVisitor.kt +++ b/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinMetadataRemappingClassVisitor.kt @@ -24,6 +24,7 @@ package net.fabricmc.loom.kotlin.remapping +import org.jetbrains.annotations.VisibleForTesting import org.objectweb.asm.AnnotationVisitor import org.objectweb.asm.ClassVisitor import org.objectweb.asm.Opcodes @@ -58,4 +59,9 @@ class KotlinMetadataRemappingClassVisitor(private val remapper: Remapper, next: return result } + + @VisibleForTesting + fun getRuntimeKotlinVersion(): String { + return KotlinVersion.CURRENT.toString() + } } diff --git a/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinMetadataTinyRemapperExtension.kt b/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinMetadataTinyRemapperExtensionImpl.kt similarity index 89% rename from src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinMetadataTinyRemapperExtension.kt rename to src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinMetadataTinyRemapperExtensionImpl.kt index 1ba80919..5ba16756 100644 --- a/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinMetadataTinyRemapperExtension.kt +++ b/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinMetadataTinyRemapperExtensionImpl.kt @@ -24,12 +24,13 @@ package net.fabricmc.loom.kotlin.remapping +import net.fabricmc.loom.util.kotlin.KotlinMetadataTinyRemapperExtension import net.fabricmc.tinyremapper.TinyRemapper import net.fabricmc.tinyremapper.api.TrClass import org.objectweb.asm.ClassVisitor -object KotlinMetadataTinyRemapperExtension : TinyRemapper.ApplyVisitorProvider, TinyRemapper.Extension { - override fun insertApplyVisitor(cls: TrClass, next: ClassVisitor): ClassVisitor { +object KotlinMetadataTinyRemapperExtensionImpl : KotlinMetadataTinyRemapperExtension { + override fun insertApplyVisitor(cls: TrClass, next: ClassVisitor?): ClassVisitor { return KotlinMetadataRemappingClassVisitor(cls.environment.remapper, next) } diff --git a/src/test/groovy/net/fabricmc/loom/test/LoomTestConstants.groovy b/src/test/groovy/net/fabricmc/loom/test/LoomTestConstants.groovy index a0e5112f..45642ec9 100644 --- a/src/test/groovy/net/fabricmc/loom/test/LoomTestConstants.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/LoomTestConstants.groovy @@ -28,7 +28,7 @@ import org.gradle.util.GradleVersion class LoomTestConstants { public final static String DEFAULT_GRADLE = GradleVersion.current().getVersion() - public final static String PRE_RELEASE_GRADLE = "7.5-20220329233654+0000" + public final static String PRE_RELEASE_GRADLE = "7.5-20220415181004+0000" public final static String[] STANDARD_TEST_VERSIONS = [DEFAULT_GRADLE, PRE_RELEASE_GRADLE] } diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/kotlin/KotlinRemapperClassloaderTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/kotlin/KotlinRemapperClassloaderTest.groovy new file mode 100644 index 00000000..bf0bcecf --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/kotlin/KotlinRemapperClassloaderTest.groovy @@ -0,0 +1,96 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2022 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.test.unit.kotlin + +import net.fabricmc.loom.util.kotlin.KotlinClasspath +import net.fabricmc.loom.util.kotlin.KotlinRemapperClassloader +import net.fabricmc.tinyremapper.api.TrClass +import net.fabricmc.tinyremapper.api.TrEnvironment +import net.fabricmc.tinyremapper.api.TrRemapper +import org.objectweb.asm.ClassReader +import org.objectweb.asm.tree.ClassNode +import spock.lang.Specification + +class KotlinRemapperClassloaderTest extends Specification { + private static String KOTLIN_VERSION = "1.6.10" + private static String KOTLIN_URL = "https://repo1.maven.org/maven2/org/jetbrains/kotlin/kotlin-stdlib/${KOTLIN_VERSION}/kotlin-stdlib-${KOTLIN_VERSION}.jar" + + def "Test Koltin Remapper Classloader"() { + given: + def classLoader = KotlinRemapperClassloader.create(new TestKotlinClasspath()) + def mockTrClass = Mock(TrClass) + def mockEnv = Mock(TrEnvironment) + def mockRemapper = Mock(TrRemapper) + + mockRemapper.map(_) >> { args -> args[0] } + mockRemapper.mapMethodDesc(_) >> { args -> args[0] } + + mockEnv.remapper >> mockRemapper + mockTrClass.environment >> mockEnv + + def classReader = new ClassReader(getClassBytes("TestExtensionKt")) + + when: + def extension = classLoader.tinyRemapperExtension + def visitor = extension.insertApplyVisitor(mockTrClass, new ClassNode()) + + classReader.accept(visitor, 0) + + then: + extension != null + visitor != null + + // Ensure that the visitor is using the kotlin version specified on the classpath. + visitor.runtimeKotlinVersion == KOTLIN_VERSION + } + + private class TestKotlinClasspath implements KotlinClasspath { + @Override + String version() { + return KOTLIN_VERSION + } + + @Override + Set classpath() { + def file = downloadFile(KOTLIN_URL, "kotlin-stdlib.jar") + + return Set.of( + file.toURI().toURL() + ) + } + } + + File tempDir = File.createTempDir() + File downloadFile(String url, String name) { + File dst = new File(tempDir, name) + dst.parentFile.mkdirs() + dst << new URL(url).newInputStream() + return dst + } + + def getClassBytes(String name) { + return new File("src/test/resources/classes/${name}.class").bytes + } +} diff --git a/src/test/resources/projects/kotlin/build.gradle.kts b/src/test/resources/projects/kotlin/build.gradle.kts index 6e9bcc2b..904c20a4 100644 --- a/src/test/resources/projects/kotlin/build.gradle.kts +++ b/src/test/resources/projects/kotlin/build.gradle.kts @@ -1,8 +1,8 @@ import java.util.Properties plugins { - kotlin("jvm") version "1.6.10" - kotlin("plugin.serialization") version "1.6.10" + kotlin("jvm") version "1.6.20" + kotlin("plugin.serialization") version "1.6.20" id("fabric-loom") } @@ -17,5 +17,5 @@ dependencies { minecraft(group = "com.mojang", name = "minecraft", version = "1.16.5") mappings(group = "net.fabricmc", name = "yarn", version = "1.16.5+build.5", classifier = "v2") modImplementation("net.fabricmc:fabric-loader:0.12.12") - modImplementation(group = "net.fabricmc", name = "fabric-language-kotlin", version = "1.7.1+kotlin.1.6.10") + modImplementation(group = "net.fabricmc", name = "fabric-language-kotlin", version = "1.7.3+kotlin.1.6.20") } \ No newline at end of file diff --git a/src/test/resources/projects/kotlin/src/main/kotlin/net/fabricmc/language/kotlin/TestExtension.kt b/src/test/resources/projects/kotlin/src/main/kotlin/net/fabricmc/language/kotlin/TestExtension.kt index 2d37d24d..281850f5 100644 --- a/src/test/resources/projects/kotlin/src/main/kotlin/net/fabricmc/language/kotlin/TestExtension.kt +++ b/src/test/resources/projects/kotlin/src/main/kotlin/net/fabricmc/language/kotlin/TestExtension.kt @@ -1,6 +1,7 @@ package net.fabricmc.language.kotlin import net.minecraft.entity.Entity +import net.minecraft.util.Identifier class TestExtension { fun testExtCompile() { @@ -11,4 +12,8 @@ class TestExtension { fun Entity.testExt() { velocityDirty = true +} + +fun Identifier.testExt(): String { + return "Hello ext" } \ No newline at end of file diff --git a/src/test/resources/projects/kotlin/src/main/kotlin/net/fabricmc/language/kotlin/TestModClass.kt b/src/test/resources/projects/kotlin/src/main/kotlin/net/fabricmc/language/kotlin/TestModClass.kt index 456ce7a9..a82609a1 100644 --- a/src/test/resources/projects/kotlin/src/main/kotlin/net/fabricmc/language/kotlin/TestModClass.kt +++ b/src/test/resources/projects/kotlin/src/main/kotlin/net/fabricmc/language/kotlin/TestModClass.kt @@ -11,13 +11,15 @@ class TestModClass : ModInitializer { val logger = LogManager.getFormatterLogger("KotlinLanguageTest") override fun onInitialize() { - val json = Json.encodeToString(ExampleSerializable(Identifier("kotlin:hello"), 12.0)) + val ident = Identifier("kotlin:hello") + val json = Json.encodeToString(ExampleSerializable(ident, 12.0)) val obj = Json.decodeFromString(json) logger.info("**************************") logger.info("Hello from Kotlin TestModClass") logger.info(json) logger.info(obj) + logger.info(ident.testExt()) logger.info("**************************") } } \ No newline at end of file