diff --git a/build.gradle b/build.gradle index a3823b0..71ebab9 100644 --- a/build.gradle +++ b/build.gradle @@ -62,6 +62,9 @@ dependencies { // source code remapping implementation ('org.cadixdev:mercury:0.1.0.fabric-SNAPSHOT') + // Kapt integration + compileOnly("org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72") + // Testing testImplementation(gradleTestKit()) testImplementation("org.spockframework:spock-core:1.3-groovy-2.4") diff --git a/src/main/java/net/fabricmc/loom/AbstractPlugin.java b/src/main/java/net/fabricmc/loom/AbstractPlugin.java index 29ffee9..19dc74a 100644 --- a/src/main/java/net/fabricmc/loom/AbstractPlugin.java +++ b/src/main/java/net/fabricmc/loom/AbstractPlugin.java @@ -24,8 +24,6 @@ package net.fabricmc.loom; -import java.io.File; -import java.io.IOException; import java.util.HashSet; import java.util.Map; import java.util.Optional; @@ -47,9 +45,7 @@ import org.gradle.api.publish.PublishingExtension; import org.gradle.api.publish.maven.MavenPublication; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.bundling.AbstractArchiveTask; -import org.gradle.api.tasks.compile.JavaCompile; import org.gradle.api.tasks.javadoc.Javadoc; -import org.gradle.api.tasks.scala.ScalaCompile; import org.gradle.plugins.ide.idea.model.IdeaModel; import net.fabricmc.loom.providers.LaunchProvider; @@ -63,6 +59,9 @@ import net.fabricmc.loom.util.LoomDependencyManager; import net.fabricmc.loom.util.NestedJars; import net.fabricmc.loom.util.RemappedConfigurationEntry; import net.fabricmc.loom.util.SetupIntelijRunConfigs; +import net.fabricmc.loom.util.mixin.JavaApInvoker; +import net.fabricmc.loom.util.mixin.KaptApInvoker; +import net.fabricmc.loom.util.mixin.ScalaApInvoker; public class AbstractPlugin implements Plugin { protected Project project; @@ -88,7 +87,6 @@ public class AbstractPlugin implements Plugin { project.getExtensions().create("minecraft", LoomGradleExtension.class, project); - LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class); // Force add Mojang repository addMavenRepo(target, "Mojang", "https://libraries.minecraft.net/"); @@ -127,74 +125,43 @@ public class AbstractPlugin implements Plugin { extendsFrom("compileClasspath", Constants.MINECRAFT_NAMED); extendsFrom("runtimeClasspath", Constants.MINECRAFT_NAMED); - if (!extension.ideSync()) { - extendsFrom("annotationProcessor", Constants.MINECRAFT_NAMED); - extendsFrom("annotationProcessor", Constants.MOD_COMPILE_CLASSPATH_MAPPED); - } - extendsFrom(Constants.MINECRAFT_NAMED, Constants.MINECRAFT_DEPENDENCIES); extendsFrom("compile", Constants.MAPPINGS_FINAL); - if (!extension.ideSync()) { - extendsFrom("annotationProcessor", Constants.MAPPINGS_FINAL); - } - configureIDEs(); configureCompile(); - configureScala(); + configureMixin(); + configureMaven(); + } - Map> taskMap = project.getAllTasks(true); - - for (Map.Entry> entry : taskMap.entrySet()) { - Project project = entry.getKey(); - Set taskSet = entry.getValue(); - - for (Task task : taskSet) { - if (task instanceof JavaCompile && !(task.getName().contains("Test")) && !(task.getName().contains("test"))) { - JavaCompile javaCompileTask = (JavaCompile) task; - javaCompileTask.doFirst(task1 -> { - project.getLogger().lifecycle(":setting java compiler args"); - - try { - javaCompileTask.getOptions().getCompilerArgs().add("-AinMapFileNamedIntermediary=" + extension.getMappingsProvider().tinyMappings.getCanonicalPath()); - javaCompileTask.getOptions().getCompilerArgs().add("-AoutMapFileNamedIntermediary=" + extension.getMappingsProvider().mappingsMixinExport.getCanonicalPath()); - javaCompileTask.getOptions().getCompilerArgs().add("-AoutRefMapFile=" + new File(javaCompileTask.getDestinationDir(), extension.getRefmapName()).getCanonicalPath()); - javaCompileTask.getOptions().getCompilerArgs().add("-AdefaultObfuscationEnv=named:intermediary"); - } catch (IOException e) { - e.printStackTrace(); - } - }); - } - } + private void configureMixin() { + if (project.getPluginManager().hasPlugin("org.jetbrains.kotlin.kapt")) { + // If loom is applied after kapt, then kapt will use the AP arguments too early for loom to pass the arguments we need for mixin. + throw new IllegalArgumentException("fabric-loom must be applied BEFORE kapt in the plugins { } block."); } - configureMaven(); + // Full plugin and mappings information is only available after evaluation + project.afterEvaluate((project) -> { + project.getLogger().lifecycle("Configuring mixins for Java plugin"); + new JavaApInvoker(project).configureMixin(); + + if (project.getPluginManager().hasPlugin("scala")) { + project.getLogger().lifecycle("Configuring mixins for Scala plugin"); + new ScalaApInvoker(project).configureMixin(); + } + + if (project.getPluginManager().hasPlugin("org.jetbrains.kotlin.kapt")) { + project.getLogger().lifecycle("Configuring mixins for Kapt plugin"); + new KaptApInvoker(project).configureMixin(); + } + }); } public Project getProject() { return project; } - protected void configureScala() { - project.afterEvaluate(proj -> { - if (project.getPluginManager().hasPlugin("scala")) { - ScalaCompile task = (ScalaCompile) project.getTasks().getByName("compileScala"); - LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class); - project.getLogger().warn(":configuring scala compilation processing"); - - try { - task.getOptions().getCompilerArgs().add("-AinMapFileNamedIntermediary=" + extension.getMappingsProvider().tinyMappings.getCanonicalPath()); - task.getOptions().getCompilerArgs().add("-AoutMapFileNamedIntermediary=" + extension.getMappingsProvider().mappingsMixinExport.getCanonicalPath()); - task.getOptions().getCompilerArgs().add("-AoutRefMapFile=" + new File(task.getDestinationDir(), extension.getRefmapName()).getCanonicalPath()); - task.getOptions().getCompilerArgs().add("-AdefaultObfuscationEnv=named:intermediary"); - } catch (IOException e) { - e.printStackTrace(); - } - } - }); - } - /** * Permit to add a Maven repository to a target project. * @@ -234,11 +201,6 @@ public class AbstractPlugin implements Plugin { Javadoc javadoc = (Javadoc) project.getTasks().getByName(JavaPlugin.JAVADOC_TASK_NAME); javadoc.setClasspath(main.getOutput().plus(main.getCompileClasspath())); - if (!project.getExtensions().getByType(LoomGradleExtension.class).ideSync()) { - // Add Mixin dependencies - project.getDependencies().add(JavaPlugin.ANNOTATION_PROCESSOR_CONFIGURATION_NAME, "net.fabricmc:fabric-mixin-compile-extensions:" + Constants.MIXIN_COMPILE_EXTENSIONS_VERSION); - } - project.afterEvaluate(project1 -> { LoomGradleExtension extension = project1.getExtensions().getByType(LoomGradleExtension.class); diff --git a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java index 79100cd..72b3c61 100644 --- a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java +++ b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java @@ -311,7 +311,7 @@ public class LoomGradleExtension { public String getRefmapName() { if (refmapName == null || refmapName.isEmpty()) { String defaultRefmapName = project.getConvention().getPlugin(BasePluginConvention.class).getArchivesBaseName() + "-refmap.json"; - project.getLogger().warn("Could not find refmap definition, will be using default name: " + defaultRefmapName); + project.getLogger().info("Could not find refmap definition, will be using default name: " + defaultRefmapName); refmapName = defaultRefmapName; } diff --git a/src/main/java/net/fabricmc/loom/util/mixin/AnnotationProcessorInvoker.java b/src/main/java/net/fabricmc/loom/util/mixin/AnnotationProcessorInvoker.java new file mode 100644 index 0000000..8fa6005 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/mixin/AnnotationProcessorInvoker.java @@ -0,0 +1,118 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016, 2017, 2018 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.mixin; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +import org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.ConfigurationContainer; +import org.gradle.api.plugins.JavaPluginConvention; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.TaskCollection; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.util.Constants; + +/** + * Normally javac invokes annotation processors, but when the scala or kapt plugin are installed they will want to invoke + * the annotation processor themselves. + * See Java and Kapt implementations for a more deep understanding of the things passed by the children. + */ +public abstract class AnnotationProcessorInvoker { + protected final Project project; + private final Collection annotationProcessorConfigurations; + protected final TaskCollection invokerTasks; + + protected AnnotationProcessorInvoker(Project project, + Collection annotationProcessorConfigurations, + TaskCollection invokerTasks) { + this.project = project; + this.annotationProcessorConfigurations = annotationProcessorConfigurations; + this.invokerTasks = invokerTasks; + } + + protected abstract void passArgument(T compileTask, String key, String value); + + protected abstract File getDestinationDir(T task); + + protected final String getRefmapDestination(T task, LoomGradleExtension extension) throws IOException { + return new File(getDestinationDir(task), extension.getRefmapName()).getCanonicalPath(); + } + + private void passMixinArguments(T task) { + try { + LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class); + Map args = new HashMap() {{ + put("inMapFileNamedIntermediary", extension.getMappingsProvider().tinyMappings.getCanonicalPath()); + put("outMapFileNamedIntermediary", extension.getMappingsProvider().mappingsMixinExport.getCanonicalPath()); + put("outRefMapFile", getRefmapDestination(task, extension)); + put("defaultObfuscationEnv", "named:intermediary"); + }}; + + project.getLogger().info("Outputting refmap to dir: " + getDestinationDir(task) + " for compile task: " + task); + args.forEach((k, v) -> passArgument(task, k, v)); + } catch (IOException e) { + project.getLogger().error("Could not configure mixin annotation processors", e); + } + } + + public void configureMixin() { + ConfigurationContainer configs = project.getConfigurations(); + LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class); + + if (!extension.ideSync()) { + for (Configuration processorConfig : annotationProcessorConfigurations) { + project.getLogger().info("Adding mixin to classpath of AP config: " + processorConfig.getName()); + // Pass named MC classpath to mixin AP classpath + processorConfig.extendsFrom( + configs.getByName(Constants.MINECRAFT_NAMED), + configs.getByName(Constants.MOD_COMPILE_CLASSPATH_MAPPED), + configs.getByName(Constants.MAPPINGS_FINAL) + ); + + // Add Mixin and mixin extensions (fabric-mixin-compile-extensions pulls mixin itself too) + project.getDependencies().add(processorConfig.getName(), + "net.fabricmc:fabric-mixin-compile-extensions:" + Constants.MIXIN_COMPILE_EXTENSIONS_VERSION); + } + } + + for (T task : invokerTasks) { + passMixinArguments(task); + } + } + + static Stream getNonTestSourceSets(Project project) { + return project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets() + .stream() + .filter(sourceSet -> !sourceSet.getName().equals("test")); + } +} diff --git a/src/main/java/net/fabricmc/loom/util/mixin/JavaApInvoker.java b/src/main/java/net/fabricmc/loom/util/mixin/JavaApInvoker.java new file mode 100644 index 0000000..8ec5215 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/mixin/JavaApInvoker.java @@ -0,0 +1,63 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016, 2017, 2018 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.mixin; + +import java.io.File; +import java.util.List; +import java.util.stream.Collectors; + +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.tasks.compile.JavaCompile; + +public class JavaApInvoker extends AnnotationProcessorInvoker { + public JavaApInvoker(Project project) { + super(project, getConfigurations(project), project.getTasks().withType(JavaCompile.class)); + } + + @Override + protected void passArgument(JavaCompile compileTask, String key, String value) { + compileTask.getOptions().getCompilerArgs().add("-A" + key + "=" + value); + } + + @Override + protected File getDestinationDir(JavaCompile task) { + return task.getDestinationDir(); + } + + private static List getConfigurations(Project project) { + // java plugin generates an AP configuration for every source set based off of the getAptConfigurationName method. + return AnnotationProcessorInvoker.getNonTestSourceSets(project) + .map(sourceSet -> project.getConfigurations() + .getByName(getAptConfigurationName(sourceSet.getName())) + ).collect(Collectors.toList()); + } + + private static String getAptConfigurationName(String sourceSet) { + // This is documented by the gradle 4.6 release notes https://docs.gradle.org/4.6/release-notes.html#potential-breaking-changes + return sourceSet.equals("main") ? JavaPlugin.ANNOTATION_PROCESSOR_CONFIGURATION_NAME : sourceSet + "AnnotationProcessor"; + } +} diff --git a/src/main/java/net/fabricmc/loom/util/mixin/KaptApInvoker.java b/src/main/java/net/fabricmc/loom/util/mixin/KaptApInvoker.java new file mode 100644 index 0000000..8d6d1d1 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/mixin/KaptApInvoker.java @@ -0,0 +1,113 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016, 2017, 2018 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.mixin; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.stream.Collectors; + +import kotlin.Unit; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.tasks.compile.JavaCompile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.kotlin.gradle.internal.Kapt3KotlinGradleSubplugin; +import org.jetbrains.kotlin.gradle.plugin.KaptExtension; + +import net.fabricmc.loom.LoomGradleExtension; + +public class KaptApInvoker extends AnnotationProcessorInvoker { + private final KaptExtension kaptExtension = project.getExtensions().getByType(KaptExtension.class); + // Refmap will be written to here with mixin, then moved after JavaCompile to the correct place + private final File dummyRefmapDirectory; + + public KaptApInvoker(Project project) { + super(project, getConfigurations(project), project.getTasks().withType(JavaCompile.class)); + + try { + dummyRefmapDirectory = Files.createTempDirectory("temp_refmap").toFile(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + dummyRefmapDirectory.deleteOnExit(); + + // Needed for mixin AP to run + kaptExtension.setIncludeCompileClasspath(false); + } + + @Override + public void configureMixin() { + super.configureMixin(); + + for (JavaCompile task : invokerTasks) { + // Kapt only allows specifying javac args to all annotation processors at once. So we need to specify some dummy + // target location for the refmap and then move it to the correct place for each sourceset + task.doLast(t -> { + try { + LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class); + Path src = Paths.get(getRefmapDestination(task, extension)); + Path dest = Paths.get(task.getDestinationDir().toString(), extension.getRefmapName()); + + // Possible that no mixin annotations exist + if (Files.exists(src)) { + project.getLogger().info("Copying refmap from " + src + " to " + dest); + Files.move(src, dest); + } + } catch (IOException e) { + project.getLogger().warn("Could not move refmap generated by kapt for task " + task, e); + } + }); + } + } + + @NotNull + private static List getConfigurations(Project project) { + // Kapt generates an AP configuration for every source set based off of the getKaptConfigurationName method. + return AnnotationProcessorInvoker.getNonTestSourceSets(project) + .map(sourceSet -> project.getConfigurations() + .getByName(Kapt3KotlinGradleSubplugin.Companion.getKaptConfigurationName(sourceSet.getName())) + ).collect(Collectors.toList()); + } + + @Override + protected void passArgument(JavaCompile compileTask, String key, String value) { + // Note: this MUST be run early on, before kapt uses this data, and there is only a point to setting the value once since + // kapt shares the options with all java compilers + kaptExtension.arguments(args -> { + args.arg(key, value); + return Unit.INSTANCE; + }); + } + + @Override + protected File getDestinationDir(JavaCompile task) { + return dummyRefmapDirectory; + } +} diff --git a/src/main/java/net/fabricmc/loom/util/mixin/ScalaApInvoker.java b/src/main/java/net/fabricmc/loom/util/mixin/ScalaApInvoker.java new file mode 100644 index 0000000..ba1ad82 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/mixin/ScalaApInvoker.java @@ -0,0 +1,50 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016, 2017, 2018 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.mixin; + +import java.io.File; + +import com.google.common.collect.ImmutableList; +import org.gradle.api.Project; +import org.gradle.api.tasks.scala.ScalaCompile; + +public class ScalaApInvoker extends AnnotationProcessorInvoker { + public ScalaApInvoker(Project project) { + super(project, + // Scala just uses the java AP configuration afaik. This of course assumes the java AP also gets configured. + ImmutableList.of(), + project.getTasks().withType(ScalaCompile.class)); + } + + @Override + protected void passArgument(ScalaCompile compileTask, String key, String value) { + compileTask.getOptions().getCompilerArgs().add("-A" + key + "=" + value); + } + + @Override + protected File getDestinationDir(ScalaCompile task) { + return task.getDestinationDir(); + } +}