diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d85bf73..f39c0eb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,43 +2,52 @@ version = "1.0" [versions] -accessWidener = "1.1.0" +accessWidener = "2.1.0" asm = "9.4" -checker = "3.26.0" +checker = "3.27.0" forgeAutoRenamingTool = "0.1.24" -forgeFlower = "1.5.605.9" +forgeFlower = "1.5.605.10" ideaExt = "1.1.6" immutables = "2.9.2" indra = "3.0.1" junit = "5.9.1" +mammoth = "1.3.0" mergeTool = "1.1.5" [libraries] # shared checkerQual = { module = "org.checkerframework:checker-qual", version = "3.26.0" } +immutables-value = { module = "org.immutables:value", version.ref = "immutables" } +immutables-gson = { module = "org.immutables:gson", version.ref = "immutables" } +immutables-metainf = { module = "org.immutables:metainf", version.ref = "immutables" } +mammoth = { module = "net.kyori:mammoth", version.ref = "mammoth" } +slf4j = { module = "org.slf4j:slf4j-api", version = "1.7.36" } # downloader-apache-http apache-httpClient5 = { module = "org.apache.httpcomponents.client5:httpclient5", version = "5.1.3" } +# forgeautorenamingtool-spi +joptSimple = { module = "net.sf.jopt-simple:jopt-simple", version = "6.0-alpha-3" } + # gradle-plugin accessWidener = { module = "net.fabricmc:access-widener", version.ref = "accessWidener" } asm = { module = "org.ow2.asm:asm", version.ref = "asm" } asm-commons = { module = "org.ow2.asm:asm-commons", version.ref = "asm" } +asm-tree = { module = "org.ow2.asm:asm-tree", version.ref = "asm" } asm-util = { module = "org.ow2.asm:asm-util", version.ref = "asm" } forgeAutoRenamingTool = { module = "net.minecraftforge:ForgeAutoRenamingTool", version.ref = "forgeAutoRenamingTool" } forgeFlower = { module = "net.minecraftforge:forgeflower", version.ref = "forgeFlower" } -gson = { module = "com.google.code.gson:gson", version = "2.9.1" } +gson = { module = "com.google.code.gson:gson", version = "2.10" } ideaExt = { module = "gradle.plugin.org.jetbrains.gradle.plugin.idea-ext:gradle-idea-ext", version.ref = "ideaExt" } -immutables-value = { module = "org.immutables:value", version.ref = "immutables" } -immutables-gson = { module = "org.immutables:gson", version.ref = "immutables" } junit-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" } junit-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" } mergeTool = { module = "net.minecraftforge:mergetool", version.ref = "mergeTool" } shadowPlugin = { module = "com.github.jengelman.gradle.plugins:shadow", version = "6.1.0" } +srgUtils = { module = "net.minecraftforge:srgutils", version = "0.4.13"} [plugins] -eclipseApt = { id = "com.diffplug.eclipse.apt", version = "3.39.0" } -gradlePluginPublish = { id = "com.gradle.plugin-publish", version = "0.21.0" } +eclipseApt = { id = "com.diffplug.eclipse.apt", version = "3.40.0" } +gradlePluginPublish = { id = "com.gradle.plugin-publish", version = "1.0.0" } ideaExt = { id = "org.jetbrains.gradle.plugin.idea-ext", version.ref = "ideaExt" } indra = { id = "net.kyori.indra", version.ref = "indra" } indra-git = { id = "net.kyori.indra.git", version.ref = "indra" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 5b537d8..b587cba 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -20,7 +20,15 @@ findProject(":gradle-plugin")?.apply { projectDir = file("subprojects/gradle-plugin") } -sequenceOf("resolver-core", "downloader-apache-http", "downloader-jdk-http").forEach { +sequenceOf( + "resolver-core", + "downloader-apache-http", + "downloader-jdk-http", + "forgeautorenamingtool-spi", + "forgeautorenamingtool-transformers", + "forgeautorenamingtool-transformer-accesswidener", + "plugin-remapper" +).forEach { include(it) findProject(":$it")?.apply { name = "vanillagradle-$it" diff --git a/subprojects/forgeautorenamingtool-spi/build.gradle.kts b/subprojects/forgeautorenamingtool-spi/build.gradle.kts new file mode 100644 index 0000000..c187ad2 --- /dev/null +++ b/subprojects/forgeautorenamingtool-spi/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + alias(libs.plugins.indra.publishing) +} + +dependencies { + compileOnlyApi(libs.checkerQual) + api(libs.forgeAutoRenamingTool) + api(libs.joptSimple) + implementation(libs.slf4j) + + compileOnlyApi(variantOf(libs.immutables.metainf) { classifier("annotations" )}) + annotationProcessor(libs.immutables.metainf) +} diff --git a/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/ForgeAutoRenamingToolInvoker.java b/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/ForgeAutoRenamingToolInvoker.java new file mode 100644 index 0000000..1910947 --- /dev/null +++ b/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/ForgeAutoRenamingToolInvoker.java @@ -0,0 +1,346 @@ +/* + * This file is part of VanillaGradle, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * 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 org.spongepowered.gradle.vanilla.renamer.spi; + +import static java.util.Objects.requireNonNull; + +import joptsimple.OptionException; +import joptsimple.OptionParser; +import joptsimple.OptionSet; +import joptsimple.OptionSpec; +import joptsimple.OptionSpecBuilder; +import net.minecraftforge.fart.api.Renamer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.ServiceConfigurationError; +import java.util.ServiceLoader; +import java.util.stream.Stream; + +public final class ForgeAutoRenamingToolInvoker { + private static final Logger LOGGER = LoggerFactory.getLogger(ForgeAutoRenamingToolInvoker.class); + + private ForgeAutoRenamingToolInvoker() { + } + + public static void main(final String... args) { + final Renamer renamer; + try { + renamer = ForgeAutoRenamingToolInvoker.createRenamer(args); + } catch (TransformerProvisionException e) { + LOGGER.error("Failed to provision the transformer '{}': {}", e.transformerId(), e.getMessage()); + if (e.getCause() != null) { + LOGGER.error("Cause: ", e.getCause()); + } + System.exit(2); + return; + } + + try { + renamer.run(); + } catch (final Exception ex) { + LOGGER.error("Failed to execute Renamer run with arguments {}", args, ex); + System.exit(1); + } + } + + public static Renamer createRenamer(final String... args) throws TransformerProvisionException { + return createRenamer(Arrays.asList(args)); + } + + public static Renamer createRenamer(final List args) throws TransformerProvisionException { + final ProviderHolder providers = ProviderHolder.INSTANCE.get(); + final OptionSet parsed; + try { + parsed = providers.parser.parse(expandArgs(new ArrayList<>(args)).toArray(new String[0])); + } catch (final OptionException ex) { + if (LOGGER.isWarnEnabled()) { + final StringWriter helpHolder = new StringWriter(); + try { + providers.parser.printHelpOn(helpHolder); + } catch (final IOException ex2) { + throw new IllegalStateException("Failed to write help", ex2); + } + LOGGER.warn(helpHolder.toString()); + } + throw new TransformerProvisionException("Failed to parse renamer options due to errors in the options " + ex.options() + ": " + ex.getMessage()); + } catch (final IOException ex) { + throw new TransformerProvisionException("Failed to read an argument file", ex); + } + + final Renamer.Builder builder = Renamer.builder() + .logger(LOGGER::info) + .debug(LOGGER::debug) + .input(parsed.valueOf(providers.inputO)) + .output(parsed.valueOf(providers.outputO)) + .threads(parsed.valueOf(providers.threadsO)); + + for (final File lib : parsed.valuesOf(providers.libO)) { + builder.lib(lib); + } + + for (final TransformerProvider provider : providers.providers) { + if (parsed.has(providers.triggerOptions.get(provider))) { + try { + builder.add(requireNonNull( + provider.create(parsed), + () -> "The provider " + provider.id() + " returned a null value when creating a transformer" + )); + } catch (final TransformerProvisionException ex) { + ex.initId(provider.id()); + throw ex; + } + } + } + + return builder.build(); + } + + /** + * Expand {@code --cfg }, {@code --cfg=}, and {@code @argfile} to include the options provided. + * + * @param args the arguments to expand + * @return the expanded arguments + */ + private static List expandArgs(final List args) throws IOException { + final List ret = new ArrayList<>(args.size()); + boolean disableAtFiles = false; + + // TODO: can this be built into jopt-simple somehow? + for (int i = 0, length = args.size(); i < length; i++) { + final String arg = args.get(i); + if (arg.equals("--cfg")) { + if (i + 1 == length) { + throw new IllegalArgumentException("No value specified for '--cfg'"); + } + disableAtFiles |= !ForgeAutoRenamingToolInvoker.consumeArgFile(args.get(++i), ret); + } else if (arg.equals("--cfg=")) { + disableAtFiles |= !ForgeAutoRenamingToolInvoker.consumeArgFile(arg.substring("--cfg=".length()), ret); + } else if (!disableAtFiles && arg.startsWith("@")) { + if (arg.startsWith("@@")) { + ret.add(arg.substring(1)); + } else { + disableAtFiles = !ForgeAutoRenamingToolInvoker.consumeArgFile(arg.substring(1), ret); + } + } else if (arg.equals("--disable-@files")){ + disableAtFiles = true; + } else { + ret.add(arg); + } + } + + return ret; + } + + private static boolean consumeArgFile(final String fileName, final List ret) throws IOException { + // TODO: implement the full spec (https://docs.oracle.com/en/java/javase/17/docs/specs/man/java.html#java-command-line-argument-files) + boolean shouldContinue = true; + try (final Stream lines = Files.lines(Paths.get(fileName), StandardCharsets.UTF_8)) { + for (final Iterator it = lines.iterator(); it.hasNext();) { + final String line = it.next(); + + if (line.equals("--disable-@files")) { + shouldContinue = false; + continue; + } + ret.add(line); + } + } + + return shouldContinue; + } + + static class ProviderHolder { + + private static final ThreadLocal INSTANCE = ThreadLocal.withInitial(ProviderHolder::create); + + final List providers; + final Map> triggerOptions; + final OptionParser parser; + + // Built-in options + + final OptionSpec inputO; + final OptionSpec outputO; + final OptionSpec libO; + final OptionSpec threadsO; + + ProviderHolder(final List providers, final Map> triggerOptions, final OptionParser parser) { + this.providers = Collections.unmodifiableList(new ArrayList<>(providers)); + this.triggerOptions = Collections.unmodifiableMap(triggerOptions); + this.parser = parser; + + this.inputO = parser.accepts("input", "Input jar file") + .withRequiredArg() + .ofType(File.class); + this.outputO = parser.accepts("output", "Output jar file") + .withRequiredArg() + .ofType(File.class); + this.libO = parser.acceptsAll(Arrays.asList("lib", "e"), "Additional libraries to use for inheritance") + .withRequiredArg() + .ofType(File.class); + this.threadsO = parser.accepts("threads", "Number of threads to use when processing") + .withRequiredArg() + .ofType(Integer.class) + .defaultsTo(Runtime.getRuntime().availableProcessors()); + } + + static ProviderHolder create() { + final List transformers = ProviderHolder.discoverTransformers(); + if (!validateTransformers(transformers)) { + throw new IllegalStateException("Invalid transformers picked up, see log output for more information."); + } + final Map> triggerOptions = new HashMap<>(); + final OptionParser parser = ProviderHolder.collectOptions(transformers, triggerOptions); + return new ProviderHolder(transformers, triggerOptions, parser); + } + + static List discoverTransformers() { + final List results = new ArrayList<>(); + final ServiceLoader loader = ServiceLoader.load(TransformerProvider.class, TransformerProvider.class.getClassLoader()); + + for (final Iterator it = loader.iterator(); it.hasNext();) { + try { + final TransformerProvider provider = it.next(); + results.add(provider); + LOGGER.debug( + "Discovered transformer provider '{}' in {} from {}", + provider.id(), + provider.getClass(), + provider.getClass().getProtectionDomain().getCodeSource() + ); + } catch (final ServiceConfigurationError ex) { + LOGGER.error("Failed to configure transformer provider", ex); + } + } + return results; + } + + static boolean validateTransformers(final List transformers) { + boolean success = true; + for (final TransformerProvider provider : transformers) { + if (provider.id() == null) { + LOGGER.error("Transformer provider in class {} has a null ID", provider.getClass()); + success = false; + continue; + } + if (!TransformerProvider.ID_PATTERN.matcher(provider.id()).matches()) { + LOGGER.error("The transformer provider '{}' in {} has an ID that does not match the regex {}", provider.id(), provider.getClass(), TransformerProvider.ID_PATTERN_REGEX); + success = false; + } + } + + return success; + } + + static OptionParser collectOptions(final List providers, final Map> triggerOptions) { + final OptionParser parser = new OptionParser(); + + final class OptionConsumerImpl implements TransformerProvider.OptionConsumer { + private final OptionSpec baseArg; + String prefix; + + OptionConsumerImpl(final OptionSpec baseArg, final String prefix) { + this.baseArg = baseArg; + this.prefix = prefix; + } + + private String prefixArgument(final String argument) { + if (this.prefix == null) { + throw new IllegalStateException("Tried to register argument outside of init() method"); + } + if (!TransformerProvider.ID_PATTERN.matcher(argument).matches()) { + throw new IllegalArgumentException("The provided argument '" + argument + "' does not match the required pattern " + TransformerProvider.ID_PATTERN_REGEX); + } + return this.prefix + "-" + argument; + } + + private List prefixArguments(final List argument) { + final List out = new ArrayList<>(argument.size()); + for (String arg : argument) { + out.add(this.prefixArgument(arg)); + } + return out; + } + + @Override + public OptionSpecBuilder accepts(final String argument) { + return parser.accepts(this.prefixArgument(argument)) + .availableIf(this.baseArg); + } + + @Override + public OptionSpecBuilder accepts(final String argument, final String description) { + return parser.accepts(this.prefixArgument(argument), description) + .availableIf(this.baseArg); + } + + @Override + public OptionSpecBuilder accepts(final List arguments) { + return parser.acceptsAll(this.prefixArguments(arguments)) + .availableIf(this.baseArg); + } + + @Override + public OptionSpecBuilder accepts(final List arguments, final String description) { + return parser.acceptsAll(this.prefixArguments(arguments), description) + .availableIf(this.baseArg); + } + } + + for (final TransformerProvider provider : providers) { + String id = ""; + try { + id = provider.id(); + final OptionSpec triggerArg = provider.decorateTriggerOption(parser.accepts(id, provider.description())); + + final OptionConsumerImpl optionConsumer = new OptionConsumerImpl(triggerArg, id); + provider.init(optionConsumer); + optionConsumer.prefix = null; + triggerOptions.put(provider, triggerArg); + } catch (final Throwable thr) { + LOGGER.error("Error occurred while preparing options for transformer provider {} (in class {}):", id, provider.getClass(), thr); + } + } + + return parser; + } + + } + +} diff --git a/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/TransformerProvider.java b/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/TransformerProvider.java new file mode 100644 index 0000000..7410d50 --- /dev/null +++ b/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/TransformerProvider.java @@ -0,0 +1,171 @@ +/* + * This file is part of VanillaGradle, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * 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 org.spongepowered.gradle.vanilla.renamer.spi; + +import joptsimple.OptionSet; +import joptsimple.OptionSpec; +import joptsimple.OptionSpecBuilder; +import net.minecraftforge.fart.api.Transformer; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.common.value.qual.MatchesRegex; + +import java.util.List; +import java.util.regex.Pattern; + +/** + * A service interface implemented to provide a {@link Transformer.Factory}. + * + *

Each implementation can provide exactly zero or one + * {@link Transformer.Factory} instance.

+ * + *

The {@link #create(OptionSet)} method may be called multiple times over + * the lifetime of a provider instance. Implementations must + * read options on every call.

+ * + * @since 0.2.1 + */ +public interface TransformerProvider { + + /** + * The regex used to compile {@link #ID_PATTERN}, describing the valid + * characters in provider IDs and argument names. + * + * @since 0.2.1 + */ + String ID_PATTERN_REGEX = "[a-z][a-z0-9-]*"; + + /** + * A pre-compiled pattern for the regex {@value #ID_PATTERN_REGEX}. + * + * @since 0.2.1 + */ + Pattern ID_PATTERN = Pattern.compile(TransformerProvider.ID_PATTERN_REGEX); + + /** + * Provide a name. + * + *

This will be used to produce an option {@code --}, which will + * cause this transformer to be activated.

+ * + *

Provider IDs must match the pattern {@value #ID_PATTERN_REGEX}.

+ * + * @return the name used to identify this transformer provider + * @since 0.2.1 + */ + @NonNull @MatchesRegex(TransformerProvider.ID_PATTERN_REGEX) String id(); + + /** + * A user-readable description of the transformer provided by this service implementation. + * + *

By default, this returns a generic message.

+ * + * @return the description + */ + default @NonNull String description() { + return "Activate the '" + this.id() + "' transformer"; + } + + /** + * Optional method to allow adding some sort of flag to the trigger option. + * + * @param orig the initial trigger option for this transformer + * @return a decorated trigger option, if desired + * @since 0.2.1 + */ + default OptionSpec decorateTriggerOption(final @NonNull OptionSpecBuilder orig) { + return orig; + } + + /** + * Register arguments that this transformer should respond to. + * + *

The provider instance should hold on to the returned + * {@link OptionSpecBuilder} instances to read values from the parsed + * option set.

+ * + * @param parser the parser to register options with + * @since 0.2.1 + */ + default void init(final @NonNull OptionConsumer parser) {} + + /** + * Using the provided options, create a transformer factory. + * + *

This method will only be invoked if the transformer is requested.

+ * + * @param options the options parsed from user input + * @return a factory, if requested from the provided options, or {@code null} + * @since 0.2.1 + */ + Transformer.Factory create(final @NonNull OptionSet options) throws TransformerProvisionException; + + /** + * A consumer for namespaced parser options. + * + * @since 0.2.1 + */ + interface OptionConsumer { + + /** + * Create an argument that has one single name. + * + * @param argument the argument label + * @return a new option spec builder + * @since 0.2.1 + */ + OptionSpecBuilder accepts(final @MatchesRegex(TransformerProvider.ID_PATTERN_REGEX) String argument); + + /** + * Create an argument with a description. + * + * @param argument the argument label + * @param description a description for the argument, shown in help output + * @return a new option spec builder + * @since 0.2.1 + */ + OptionSpecBuilder accepts(final @MatchesRegex(TransformerProvider.ID_PATTERN_REGEX) String argument, final String description); + + /** + * Create an argument with several aliases. + * + * @param arguments the argument aliases + * @return a new option spec builder + * @since 0.2.1 + */ + OptionSpecBuilder accepts(final List<@MatchesRegex(TransformerProvider.ID_PATTERN_REGEX) String> arguments); + + /** + * Create an argument with several aliases and a description. + * + * @param arguments the argument aliases + * @param description a description for the argument, shown in help output + * @return a new option spec builder + * @since 0.2.1 + */ + OptionSpecBuilder accepts(final List<@MatchesRegex(TransformerProvider.ID_PATTERN_REGEX) String> arguments, final String description); + + } + +} diff --git a/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/TransformerProvisionException.java b/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/TransformerProvisionException.java new file mode 100644 index 0000000..20b044c --- /dev/null +++ b/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/TransformerProvisionException.java @@ -0,0 +1,78 @@ +/* + * This file is part of VanillaGradle, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * 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 org.spongepowered.gradle.vanilla.renamer.spi; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.Objects; + +/** + * Exception that can be thrown when a transformer is being created, + * if invalid data is provided. + * + * @since 0.2.1 + */ +public class TransformerProvisionException extends Exception { + + private static final long serialVersionUID = -2631263839948152166L; + + private String transformerId = ""; + + /** + * Create a new exception with a detail message but no cause. + * + * @param message the message + * @since 0.2.1 + */ + public TransformerProvisionException(final String message) { + super(message); + } + + /** + * Create a new exception with a detail message and cause + * + * @param message the message + * @param cause the cause + * @since 0.2.1 + */ + public TransformerProvisionException(final String message, final Throwable cause) { + super(message, cause); + } + + /** + * Get the ID of the transformer that failed to be created. + * + * @return the transformer ID + * @since 0.2.1 + */ + @NonNull String transformerId() { + return this.transformerId; + } + + void initId(final String id) { + this.transformerId = Objects.requireNonNull(id, "id"); + } + +} diff --git a/subprojects/gradle-plugin/src/main/java/org/spongepowered/gradle/vanilla/internal/transformer/LocalVariableNameFixer.java b/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/builtin/AnnotationFixerProvider.java similarity index 65% rename from subprojects/gradle-plugin/src/main/java/org/spongepowered/gradle/vanilla/internal/transformer/LocalVariableNameFixer.java rename to subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/builtin/AnnotationFixerProvider.java index 93448c4..fc20dee 100644 --- a/subprojects/gradle-plugin/src/main/java/org/spongepowered/gradle/vanilla/internal/transformer/LocalVariableNameFixer.java +++ b/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/builtin/AnnotationFixerProvider.java @@ -22,22 +22,27 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package org.spongepowered.gradle.vanilla.internal.transformer; +package org.spongepowered.gradle.vanilla.renamer.spi.builtin; +import joptsimple.OptionSet; import net.minecraftforge.fart.api.Transformer; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassWriter; -import org.spongepowered.gradle.vanilla.internal.asm.LocalVariableNamingClassVisitor; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.immutables.metainf.Metainf; +import org.spongepowered.gradle.vanilla.renamer.spi.TransformerProvider; -final class LocalVariableNameFixer implements Transformer { +@Metainf.Service +public final class AnnotationFixerProvider implements TransformerProvider { + + private static final String ID = "ann-fix"; + + @Override + public @NonNull String id() { + return AnnotationFixerProvider.ID; + } @Override - public ClassEntry process(final ClassEntry entry) { - final ClassReader reader = new ClassReader(entry.getData()); - final ClassWriter writer = new ClassWriter(reader, 0); - final LocalVariableNamingClassVisitor visitor = new LocalVariableNamingClassVisitor(writer); - reader.accept(visitor, 0); - return ClassEntry.create(entry.getName(), entry.getTime(), writer.toByteArray()); + public Transformer.Factory create(final @NonNull OptionSet options) { + return Transformer.parameterAnnotationFixerFactory(); } } diff --git a/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/builtin/EnumConverter.java b/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/builtin/EnumConverter.java new file mode 100644 index 0000000..1e51046 --- /dev/null +++ b/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/builtin/EnumConverter.java @@ -0,0 +1,32 @@ +/* + * This file is part of VanillaGradle, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * 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 org.spongepowered.gradle.vanilla.renamer.spi.builtin; + +final class EnumConverter> extends joptsimple.util.EnumConverter { + + EnumConverter(final Class clazz) { + super(clazz); + } +} diff --git a/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/builtin/FernFlowerLineNumberProvider.java b/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/builtin/FernFlowerLineNumberProvider.java new file mode 100644 index 0000000..8103e7d --- /dev/null +++ b/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/builtin/FernFlowerLineNumberProvider.java @@ -0,0 +1,60 @@ +/* + * This file is part of VanillaGradle, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * 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 org.spongepowered.gradle.vanilla.renamer.spi.builtin; + +import joptsimple.OptionSet; +import joptsimple.OptionSpec; +import joptsimple.OptionSpecBuilder; +import net.minecraftforge.fart.api.Transformer; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.immutables.metainf.Metainf; +import org.spongepowered.gradle.vanilla.renamer.spi.TransformerProvider; + +import java.io.File; + +@Metainf.Service +public final class FernFlowerLineNumberProvider implements TransformerProvider { + + private static final String ID = "ff-line-numbers"; + + private @MonotonicNonNull OptionSpec triggerOption; + + @Override + public @NonNull String id() { + return FernFlowerLineNumberProvider.ID; + } + + @Override + public OptionSpec decorateTriggerOption(final @NonNull OptionSpecBuilder orig) { + return this.triggerOption = orig.withRequiredArg() + .ofType(File.class); + } + + @Override + public Transformer.Factory create(final @NonNull OptionSet options) { + return Transformer.fernFlowerLineFixerFactory(options.valueOf(this.triggerOption)); + } +} diff --git a/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/builtin/IdentifierFixerProvider.java b/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/builtin/IdentifierFixerProvider.java new file mode 100644 index 0000000..4823fab --- /dev/null +++ b/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/builtin/IdentifierFixerProvider.java @@ -0,0 +1,60 @@ +/* + * This file is part of VanillaGradle, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * 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 org.spongepowered.gradle.vanilla.renamer.spi.builtin; + +import joptsimple.OptionSet; +import joptsimple.OptionSpec; +import joptsimple.OptionSpecBuilder; +import net.minecraftforge.fart.api.IdentifierFixerConfig; +import net.minecraftforge.fart.api.Transformer; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.immutables.metainf.Metainf; +import org.spongepowered.gradle.vanilla.renamer.spi.TransformerProvider; + +@Metainf.Service +public final class IdentifierFixerProvider implements TransformerProvider { + + private static final String ID = "ids-fix"; + + private @MonotonicNonNull OptionSpec triggerOption; + + @Override + public @NonNull String id() { + return IdentifierFixerProvider.ID; + } + + @Override + public OptionSpec decorateTriggerOption(final @NonNull OptionSpecBuilder orig) { + return this.triggerOption = orig.withOptionalArg() + .withValuesConvertedBy(new EnumConverter<>(IdentifierFixerConfig.class)) + .defaultsTo(IdentifierFixerConfig.ALL); + } + + @Override + public Transformer.Factory create(final @NonNull OptionSet options) { + return Transformer.identifierFixerFactory(options.valueOf(this.triggerOption)); + } +} diff --git a/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/builtin/RecordFixerProvider.java b/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/builtin/RecordFixerProvider.java new file mode 100644 index 0000000..18bfafd --- /dev/null +++ b/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/builtin/RecordFixerProvider.java @@ -0,0 +1,48 @@ +/* + * This file is part of VanillaGradle, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * 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 org.spongepowered.gradle.vanilla.renamer.spi.builtin; + +import joptsimple.OptionSet; +import net.minecraftforge.fart.api.Transformer; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.immutables.metainf.Metainf; +import org.spongepowered.gradle.vanilla.renamer.spi.TransformerProvider; + +@Metainf.Service +public final class RecordFixerProvider implements TransformerProvider { + + private static final String ID = "record-fix"; + + @Override + public @NonNull String id() { + return RecordFixerProvider.ID; + } + + @Override + public Transformer.Factory create(final @NonNull OptionSet options) { + return Transformer.recordFixerFactory(); + } + +} diff --git a/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/builtin/RemapperProvider.java b/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/builtin/RemapperProvider.java new file mode 100644 index 0000000..261b2c3 --- /dev/null +++ b/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/builtin/RemapperProvider.java @@ -0,0 +1,88 @@ +/* + * This file is part of VanillaGradle, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * 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 org.spongepowered.gradle.vanilla.renamer.spi.builtin; + +import joptsimple.OptionSet; +import joptsimple.OptionSpec; +import joptsimple.OptionSpecBuilder; +import net.minecraftforge.fart.api.Transformer; +import net.minecraftforge.srgutils.IMappingFile; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.immutables.metainf.Metainf; +import org.spongepowered.gradle.vanilla.renamer.spi.TransformerProvider; +import org.spongepowered.gradle.vanilla.renamer.spi.TransformerProvisionException; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +@Metainf.Service +public final class RemapperProvider implements TransformerProvider { + + private static final String ID = "map"; + + private @MonotonicNonNull OptionSpec triggerOption; + private @MonotonicNonNull OptionSpec reverseO; + + @Override + public @NonNull String id() { + return RemapperProvider.ID; + } + + @Override + public OptionSpec decorateTriggerOption(final @NonNull OptionSpecBuilder orig) { + return this.triggerOption = orig.withRequiredArg().ofType(File.class); + } + + @Override + public void init(final @NonNull OptionConsumer parser) { + this.reverseO = parser.accepts("reverse", "Reverse provided mapping files before applying"); + } + + @Override + public Transformer.Factory create(final @NonNull OptionSet options) throws TransformerProvisionException { + final List files = options.valuesOf(this.triggerOption); + final boolean reverse = options.has(this.reverseO); + IMappingFile combined = null; + for (final File file : files) { + try { + IMappingFile singleMapping = IMappingFile.load(file); + if (reverse) { + singleMapping = singleMapping.reverse(); + } + if (combined == null) { + combined = singleMapping; + } else { + throw new TransformerProvisionException("Multiple mappings files are not yet supported"); + // combined = combined.plus(singleMapping); + } + } catch (final IOException ex) { + throw new TransformerProvisionException("Failed to read mappings from " + file, ex); + } + } + return Transformer.renamerFactory(combined); + } +} diff --git a/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/builtin/SignatureStripperProvider.java b/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/builtin/SignatureStripperProvider.java new file mode 100644 index 0000000..423889f --- /dev/null +++ b/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/builtin/SignatureStripperProvider.java @@ -0,0 +1,59 @@ +/* + * This file is part of VanillaGradle, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * 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 org.spongepowered.gradle.vanilla.renamer.spi.builtin; + +import joptsimple.OptionSet; +import joptsimple.OptionSpec; +import joptsimple.OptionSpecBuilder; +import net.minecraftforge.fart.api.SignatureStripperConfig; +import net.minecraftforge.fart.api.Transformer; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.immutables.metainf.Metainf; +import org.spongepowered.gradle.vanilla.renamer.spi.TransformerProvider; + +@Metainf.Service +public final class SignatureStripperProvider implements TransformerProvider { + private static final String ID = "strip-sigs"; + + private @MonotonicNonNull OptionSpec triggerOption; + + @Override + public @NonNull String id() { + return SignatureStripperProvider.ID; + } + + @Override + public OptionSpec decorateTriggerOption(final @NonNull OptionSpecBuilder orig) { + return this.triggerOption = orig.withOptionalArg() + .withValuesConvertedBy(new EnumConverter<>(SignatureStripperConfig.class)) + .defaultsTo(SignatureStripperConfig.ALL); + } + + @Override + public Transformer.Factory create(final @NonNull OptionSet options) { + return Transformer.signatureStripperFactory(options.valueOf(this.triggerOption)); + } +} diff --git a/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/builtin/SourceFixerProvider.java b/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/builtin/SourceFixerProvider.java new file mode 100644 index 0000000..6b7e8cb --- /dev/null +++ b/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/builtin/SourceFixerProvider.java @@ -0,0 +1,59 @@ +/* + * This file is part of VanillaGradle, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * 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 org.spongepowered.gradle.vanilla.renamer.spi.builtin; + +import joptsimple.OptionSet; +import joptsimple.OptionSpec; +import joptsimple.OptionSpecBuilder; +import net.minecraftforge.fart.api.SourceFixerConfig; +import net.minecraftforge.fart.api.Transformer; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.immutables.metainf.Metainf; +import org.spongepowered.gradle.vanilla.renamer.spi.TransformerProvider; + +@Metainf.Service +public final class SourceFixerProvider implements TransformerProvider { + private static final String ID = "src-fix"; + + private @MonotonicNonNull OptionSpec triggerOption; + + @Override + public @NonNull String id() { + return SourceFixerProvider.ID; + } + + @Override + public OptionSpec decorateTriggerOption(final @NonNull OptionSpecBuilder orig) { + return this.triggerOption = orig.withOptionalArg() + .withValuesConvertedBy(new EnumConverter<>(SourceFixerConfig.class)) + .defaultsTo(SourceFixerConfig.JAVA); + } + + @Override + public Transformer.Factory create(final @NonNull OptionSet options) { + return Transformer.sourceFixerFactory(options.valueOf(this.triggerOption)); + } +} diff --git a/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/builtin/package-info.java b/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/builtin/package-info.java new file mode 100644 index 0000000..9a5f511 --- /dev/null +++ b/subprojects/forgeautorenamingtool-spi/src/main/java/org/spongepowered/gradle/vanilla/renamer/spi/builtin/package-info.java @@ -0,0 +1,8 @@ +/** + * Service implementations for the transformers built into FART. + * + *

This is required for these transformers to be available from our CLI.

+ * + * @since 0.2.1 + */ +package org.spongepowered.gradle.vanilla.renamer.spi.builtin; diff --git a/subprojects/forgeautorenamingtool-transformer-accesswidener/build.gradle.kts b/subprojects/forgeautorenamingtool-transformer-accesswidener/build.gradle.kts new file mode 100644 index 0000000..7796058 --- /dev/null +++ b/subprojects/forgeautorenamingtool-transformer-accesswidener/build.gradle.kts @@ -0,0 +1,12 @@ +plugins { + alias(libs.plugins.indra.publishing) +} + +dependencies { + api(project(":vanillagradle-forgeautorenamingtool-spi")) + implementation(libs.asm) + implementation(libs.accessWidener) + + compileOnlyApi(variantOf(libs.immutables.metainf) { classifier("annotations" )}) + annotationProcessor(libs.immutables.metainf) +} diff --git a/subprojects/forgeautorenamingtool-transformer-accesswidener/src/main/java/org/spongepowered/gradle/vanilla/renamer/accesswidener/AccessWidenerEntryTransformer.java b/subprojects/forgeautorenamingtool-transformer-accesswidener/src/main/java/org/spongepowered/gradle/vanilla/renamer/accesswidener/AccessWidenerEntryTransformer.java new file mode 100644 index 0000000..7151cd9 --- /dev/null +++ b/subprojects/forgeautorenamingtool-transformer-accesswidener/src/main/java/org/spongepowered/gradle/vanilla/renamer/accesswidener/AccessWidenerEntryTransformer.java @@ -0,0 +1,129 @@ +/* + * This file is part of VanillaGradle, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * 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 org.spongepowered.gradle.vanilla.renamer.accesswidener; + +import joptsimple.OptionSet; +import joptsimple.OptionSpec; +import joptsimple.OptionSpecBuilder; +import net.fabricmc.accesswidener.AccessWidener; +import net.fabricmc.accesswidener.AccessWidenerClassVisitor; +import net.fabricmc.accesswidener.AccessWidenerReader; +import net.fabricmc.accesswidener.TransitiveOnlyFilter; +import net.minecraftforge.fart.api.Transformer; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.immutables.metainf.Metainf; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.spongepowered.gradle.vanilla.renamer.spi.TransformerProvider; +import org.spongepowered.gradle.vanilla.renamer.spi.TransformerProvisionException; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.List; + +@Metainf.Service +public final class AccessWidenerEntryTransformer implements TransformerProvider { + + private static final String ID = "access-widener"; + + private @MonotonicNonNull OptionSpec triggerOption; + + private @MonotonicNonNull OptionSpec transitiveOnlyO; + private @MonotonicNonNull OptionSpec namespaceO; + + @Override + public @NonNull String id() { + return AccessWidenerEntryTransformer.ID; + } + + @Override + public OptionSpec decorateTriggerOption(final @NonNull OptionSpecBuilder orig) { + return this.triggerOption = orig.withRequiredArg().ofType(File.class); + } + + @Override + public void init(final @NonNull OptionConsumer parser) { + this.transitiveOnlyO = parser.accepts("transitive-only", "Only apply entries marked as transitive"); + this.namespaceO = parser.accepts("ns", "The expected source namespace for access wideners").withRequiredArg(); + } + + @Override + public Transformer.Factory create(final @NonNull OptionSet options) throws TransformerProvisionException { + final List accessWideners = options.valuesOf(this.triggerOption); + final AccessWidener widener = new AccessWidener(); + final AccessWidenerReader reader; + if (options.has(this.transitiveOnlyO)) { + reader = new AccessWidenerReader(new TransitiveOnlyFilter(widener)); + } else { + reader = new AccessWidenerReader(widener); + } + + final @Nullable String expectedNS = options.valueOf(this.namespaceO); + + for (final File widenerFile : accessWideners) { + try (final BufferedReader fileReader = Files.newBufferedReader(widenerFile.toPath(), StandardCharsets.UTF_8)) { + reader.read(fileReader, expectedNS); + } catch (final IOException ex) { + throw new TransformerProvisionException("Failed to read access widener from file " + widenerFile, ex); + } + } + + return ctx -> new Action(widener); + } + + static final class Action implements Transformer { + + private final AccessWidener widener; + + Action(final AccessWidener widener) { + this.widener = widener; + } + + @Override + public ClassEntry process(final ClassEntry entry) { + // Because InnerClass attributes can be present in any class AW'd classes + // are referenced from, we have to target every class to get a correct output. + final ClassReader reader = new ClassReader(entry.getData()); + final ClassWriter writer = new ClassWriter(reader, 0); + // TODO: Expose the ASM version constant somewhere visible to this worker + final ClassVisitor visitor = AccessWidenerClassVisitor.createClassVisitor(Opcodes.ASM9, writer, this.widener); + reader.accept(visitor, 0); + if (entry.isMultiRelease()) { + return ClassEntry.create(entry.getName(), entry.getTime(), writer.toByteArray(), entry.getVersion()); + } else { + return ClassEntry.create(entry.getName(), entry.getTime(), writer.toByteArray()); + } + } + + } + +} diff --git a/subprojects/forgeautorenamingtool-transformers/build.gradle.kts b/subprojects/forgeautorenamingtool-transformers/build.gradle.kts new file mode 100644 index 0000000..0038984 --- /dev/null +++ b/subprojects/forgeautorenamingtool-transformers/build.gradle.kts @@ -0,0 +1,12 @@ +plugins { + alias(libs.plugins.indra.publishing) +} + +dependencies { + api(project(":vanillagradle-forgeautorenamingtool-spi")) + implementation(libs.asm) + implementation(libs.asm.tree) + + compileOnlyApi(variantOf(libs.immutables.metainf) { classifier("annotations" )}) + annotationProcessor(libs.immutables.metainf) +} diff --git a/subprojects/forgeautorenamingtool-transformers/src/main/java/org/spongepowered/gradle/vanilla/renamer/transformer/ASM.java b/subprojects/forgeautorenamingtool-transformers/src/main/java/org/spongepowered/gradle/vanilla/renamer/transformer/ASM.java new file mode 100644 index 0000000..6cee59c --- /dev/null +++ b/subprojects/forgeautorenamingtool-transformers/src/main/java/org/spongepowered/gradle/vanilla/renamer/transformer/ASM.java @@ -0,0 +1,36 @@ +/* + * This file is part of VanillaGradle, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * 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 org.spongepowered.gradle.vanilla.renamer.transformer; + +import org.objectweb.asm.Opcodes; + +final class ASM { + + private ASM() { + } + + static final int API = Opcodes.ASM9; + +} diff --git a/subprojects/forgeautorenamingtool-transformers/src/main/java/org/spongepowered/gradle/vanilla/renamer/transformer/FilterClassesTransformer.java b/subprojects/forgeautorenamingtool-transformers/src/main/java/org/spongepowered/gradle/vanilla/renamer/transformer/FilterClassesTransformer.java new file mode 100644 index 0000000..472fff5 --- /dev/null +++ b/subprojects/forgeautorenamingtool-transformers/src/main/java/org/spongepowered/gradle/vanilla/renamer/transformer/FilterClassesTransformer.java @@ -0,0 +1,115 @@ +/* + * This file is part of VanillaGradle, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * 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 org.spongepowered.gradle.vanilla.renamer.transformer; + +import joptsimple.OptionSet; +import joptsimple.OptionSpec; +import joptsimple.OptionSpecBuilder; +import net.minecraftforge.fart.api.Transformer; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.common.value.qual.MatchesRegex; +import org.immutables.metainf.Metainf; +import org.spongepowered.gradle.vanilla.renamer.spi.TransformerProvider; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Metainf.Service +public final class FilterClassesTransformer implements TransformerProvider { + + private static final String ID = "only"; + + private @MonotonicNonNull OptionSpec triggerOption; + + @Override + public @NonNull @MatchesRegex(TransformerProvider.ID_PATTERN_REGEX) String id() { + return FilterClassesTransformer.ID; + } + + @Override + public OptionSpec decorateTriggerOption(final @NonNull OptionSpecBuilder orig) { + return this.triggerOption = orig.withRequiredArg(); + } + + @Override + public Transformer.Factory create(final @NonNull OptionSet options) { + final List allowedPrefixes = options.valuesOf(this.triggerOption); + return ctx -> new Action(new HashSet<>(allowedPrefixes)); + } + + static final class Action implements Transformer { + + private final String[] allowedPrefixes; + + Action(final Set allowedPrefixes) { + this.allowedPrefixes = allowedPrefixes.toArray(new String[0]); + } + + @Override + public ClassEntry process(final ClassEntry entry) { + if (this.matches(entry.getName())) { + return entry; + } else { + return null; + } + } + + private boolean matches(final String className) { + if (!className.contains("/")) { + return true; + } + + for (final String pkg : this.allowedPrefixes) { + if (className.startsWith(pkg)) { + return true; + } + } + return false; + + } + + @Override + public ResourceEntry process(final ResourceEntry entry) { + return entry; + /*if (!this.matches(entry.getConfig().getService().replace('.', '/'))) { + return null; + } + + final List providers = new ArrayList<>(entry.getConfig().getProviders().size()); + for (final String provider : entry.getConfig().getProviders()) { + if (this.matches(provider.replace('.', '/'))) { + providers.add(provider); + } + } + if (providers.isEmpty()) { + return null; + } + + return new JarServiceProviderConfigurationEntry(entry.getTime(), new ServiceProviderConfiguration(entry.getConfig().getService(), providers));*/ + } + } +} diff --git a/subprojects/gradle-plugin/src/accessWiden/java/org/spongepowered/gradle/vanilla/internal/worker/AccessWidenerEntryTransformer.java b/subprojects/forgeautorenamingtool-transformers/src/main/java/org/spongepowered/gradle/vanilla/renamer/transformer/LocalVariableNameFixer.java similarity index 55% rename from subprojects/gradle-plugin/src/accessWiden/java/org/spongepowered/gradle/vanilla/internal/worker/AccessWidenerEntryTransformer.java rename to subprojects/forgeautorenamingtool-transformers/src/main/java/org/spongepowered/gradle/vanilla/renamer/transformer/LocalVariableNameFixer.java index d741c68..6b224e9 100644 --- a/subprojects/gradle-plugin/src/accessWiden/java/org/spongepowered/gradle/vanilla/internal/worker/AccessWidenerEntryTransformer.java +++ b/subprojects/forgeautorenamingtool-transformers/src/main/java/org/spongepowered/gradle/vanilla/renamer/transformer/LocalVariableNameFixer.java @@ -22,36 +22,43 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package org.spongepowered.gradle.vanilla.internal.worker; +package org.spongepowered.gradle.vanilla.renamer.transformer; -import net.fabricmc.accesswidener.AccessWidener; -import net.fabricmc.accesswidener.AccessWidenerVisitor; +import joptsimple.OptionSet; import net.minecraftforge.fart.api.Transformer; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.immutables.metainf.Metainf; import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Opcodes; +import org.spongepowered.gradle.vanilla.renamer.spi.TransformerProvider; -final class AccessWidenerEntryTransformer implements Transformer { - private final AccessWidener widener; +@Metainf.Service +public final class LocalVariableNameFixer implements TransformerProvider { - public AccessWidenerEntryTransformer(final AccessWidener widener) { - this.widener = widener; + @Override + public @NonNull String id() { + return "fix-lvt-names"; } @Override - public ClassEntry process(final ClassEntry entry) { - // Because InnerClass attributes can be present in any class AW'd classes - // are referenced from, we have to target every class to get a correct output. - final ClassReader reader = new ClassReader(entry.getData()); - final ClassWriter writer = new ClassWriter(reader, 0); - // TODO: Expose the ASM version constant somewhere visible to this worker - final ClassVisitor visitor = AccessWidenerVisitor.createClassVisitor(Opcodes.ASM9, writer, this.widener); - reader.accept(visitor, 0); - if (entry.isMultiRelease()) { - return ClassEntry.create(entry.getName(), entry.getTime(), writer.toByteArray(), entry.getVersion()); - } else { + public Transformer.Factory create(final @NonNull OptionSet options) { + return Transformer.Factory.always(Action.INSTANCE); + } + + static final class Action implements Transformer { + static final Action INSTANCE = new Action(); + + private Action() { + } + + @Override + public ClassEntry process(final ClassEntry entry) { + final ClassReader reader = new ClassReader(entry.getData()); + final ClassWriter writer = new ClassWriter(reader, 0); + final LocalVariableNamingClassVisitor visitor = new LocalVariableNamingClassVisitor(writer); + reader.accept(visitor, 0); return ClassEntry.create(entry.getName(), entry.getTime(), writer.toByteArray()); } } + } diff --git a/subprojects/gradle-plugin/src/main/java/org/spongepowered/gradle/vanilla/internal/asm/LocalVariableNamer.java b/subprojects/forgeautorenamingtool-transformers/src/main/java/org/spongepowered/gradle/vanilla/renamer/transformer/LocalVariableNamer.java similarity index 95% rename from subprojects/gradle-plugin/src/main/java/org/spongepowered/gradle/vanilla/internal/asm/LocalVariableNamer.java rename to subprojects/forgeautorenamingtool-transformers/src/main/java/org/spongepowered/gradle/vanilla/renamer/transformer/LocalVariableNamer.java index b140b91..e8f9735 100644 --- a/subprojects/gradle-plugin/src/main/java/org/spongepowered/gradle/vanilla/internal/asm/LocalVariableNamer.java +++ b/subprojects/forgeautorenamingtool-transformers/src/main/java/org/spongepowered/gradle/vanilla/renamer/transformer/LocalVariableNamer.java @@ -22,14 +22,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package org.spongepowered.gradle.vanilla.internal.asm; +package org.spongepowered.gradle.vanilla.renamer.transformer; import org.objectweb.asm.Handle; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; -import org.spongepowered.gradle.vanilla.internal.Constants; -public final class LocalVariableNamer extends MethodVisitor { +final class LocalVariableNamer extends MethodVisitor { private final boolean isStatic; private final int paramTotal; @@ -45,7 +44,7 @@ public LocalVariableNamer( final VariableScope scope, final MethodVisitor methodVisitor ) { - super(Constants.ASM_VERSION, methodVisitor); + super(ASM.API, methodVisitor); this.isStatic = isStatic; this.paramTotal = paramTotal; this.scopeTracker = scopeTracker; diff --git a/subprojects/gradle-plugin/src/main/java/org/spongepowered/gradle/vanilla/internal/asm/LocalVariableNamingClassVisitor.java b/subprojects/forgeautorenamingtool-transformers/src/main/java/org/spongepowered/gradle/vanilla/renamer/transformer/LocalVariableNamingClassVisitor.java similarity index 91% rename from subprojects/gradle-plugin/src/main/java/org/spongepowered/gradle/vanilla/internal/asm/LocalVariableNamingClassVisitor.java rename to subprojects/forgeautorenamingtool-transformers/src/main/java/org/spongepowered/gradle/vanilla/renamer/transformer/LocalVariableNamingClassVisitor.java index 865559d..b571afb 100644 --- a/subprojects/gradle-plugin/src/main/java/org/spongepowered/gradle/vanilla/internal/asm/LocalVariableNamingClassVisitor.java +++ b/subprojects/forgeautorenamingtool-transformers/src/main/java/org/spongepowered/gradle/vanilla/renamer/transformer/LocalVariableNamingClassVisitor.java @@ -22,20 +22,19 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package org.spongepowered.gradle.vanilla.internal.asm; +package org.spongepowered.gradle.vanilla.renamer.transformer; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; -import org.spongepowered.gradle.vanilla.internal.Constants; -public final class LocalVariableNamingClassVisitor extends ClassVisitor { +final class LocalVariableNamingClassVisitor extends ClassVisitor { private final VariableScopeTracker tracker = new VariableScopeTracker(); public LocalVariableNamingClassVisitor(final ClassVisitor classVisitor) { - super(Constants.ASM_VERSION, classVisitor); + super(ASM.API, classVisitor); } @Override diff --git a/subprojects/forgeautorenamingtool-transformers/src/main/java/org/spongepowered/gradle/vanilla/renamer/transformer/RecordSignatureFixer.java b/subprojects/forgeautorenamingtool-transformers/src/main/java/org/spongepowered/gradle/vanilla/renamer/transformer/RecordSignatureFixer.java new file mode 100644 index 0000000..b3f2411 --- /dev/null +++ b/subprojects/forgeautorenamingtool-transformers/src/main/java/org/spongepowered/gradle/vanilla/renamer/transformer/RecordSignatureFixer.java @@ -0,0 +1,307 @@ +/* + * This file is part of VanillaGradle, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * 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 org.spongepowered.gradle.vanilla.renamer.transformer; + +import joptsimple.OptionSet; +import net.minecraftforge.fart.api.Inheritance; +import net.minecraftforge.fart.api.Transformer; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.common.value.qual.MatchesRegex; +import org.immutables.metainf.Metainf; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.RecordComponentVisitor; +import org.objectweb.asm.Type; +import org.objectweb.asm.signature.SignatureReader; +import org.objectweb.asm.signature.SignatureVisitor; +import org.objectweb.asm.signature.SignatureWriter; +import org.objectweb.asm.tree.ClassNode; +import org.spongepowered.gradle.vanilla.renamer.spi.TransformerProvider; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; + +@Metainf.Service +public final class RecordSignatureFixer implements TransformerProvider { + + private static final String ID = "vg-record-fix"; + + @Override + public @NonNull @MatchesRegex(TransformerProvider.ID_PATTERN_REGEX) String id() { + return RecordSignatureFixer.ID; + } + + @Override + public Transformer.Factory create(final @NonNull OptionSet options) { + return ctx -> new Action(ctx.getDebug(), ctx.getInheritance()); + } + + static final class Action implements Transformer { + + private final Consumer debug; + private final Inheritance inh; + + public Action(final Consumer debug, final Inheritance inh) { + this.debug = debug; + this.inh = inh; + } + + @Override + public ClassEntry process(final ClassEntry entry) { + final ClassReader reader = new ClassReader(entry.getData()); + final ClassWriter writer = new ClassWriter(reader, 0); + + reader.accept(new Fixer(writer), 0); + return ClassEntry.create(entry.getName(), entry.getTime(), writer.toByteArray()); + } + + private class Fixer extends ClassVisitor { + + private TypeParameterCollector paramCollector; + private boolean isRecord; + private boolean patchSignature; + private final ClassVisitor originalParent; + private ClassNode node; + + public Fixer(final ClassVisitor parent) { + super(ASM.API, parent); + this.originalParent = parent; + } + + @Override + public void visit( + final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces + ) { + this.isRecord = "java/lang/Record".equals(superName); + this.patchSignature = signature == null; + if (this.isRecord && this.patchSignature) { + this.node = new ClassNode(); + this.cv = this.node; + } + // todo: validate type parameters from superinterfaces + // this would need to get signature information from bytecode + runtime classes + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public RecordComponentVisitor visitRecordComponent(final String name, final String descriptor, final String signature) { + if (signature != null && this.patchSignature) { // signature implies non-primitive type + if (this.paramCollector == null) { + this.paramCollector = new TypeParameterCollector(); + } + this.paramCollector.baseType = Type.getType(descriptor); + this.paramCollector.param = TypeParameterCollector.FIELD; + new SignatureReader(signature).accept(this.paramCollector); + } + return super.visitRecordComponent(name, descriptor, signature); + } + + @Override + public FieldVisitor visitField(final int access, final String name, final String descriptor, final String signature, final Object value) { + if (this.isRecord && signature != null && this.patchSignature) { // signature implies non-primitive type + if (this.paramCollector == null) + this.paramCollector = new TypeParameterCollector(); + this.paramCollector.baseType = Type.getType(descriptor); + this.paramCollector.param = TypeParameterCollector.FIELD; + new SignatureReader(signature).accept(this.paramCollector); + } + return super.visitField(access, name, descriptor, signature, value); + } + + @Override + public MethodVisitor visitMethod( + final int access, final String name, final String descriptor, final String signature, final String[] exceptions + ) { + if ((access & Opcodes.ACC_STATIC) == 0 && this.isRecord && signature != null + && this.patchSignature) { // signature implies non-primitive type + if (this.paramCollector == null) + this.paramCollector = new TypeParameterCollector(); + this.paramCollector.baseType = Type.getType(descriptor); + this.paramCollector.param = TypeParameterCollector.FIELD; // start out before parameters come in + new SignatureReader(signature).accept(this.paramCollector); + if (this.paramCollector.declaredParams != null) { + this.paramCollector.declaredParams.clear(); + } + } + return super.visitMethod(access, name, descriptor, signature, exceptions); + } + + @Override + public void visitEnd() { + if (this.isRecord && this.patchSignature && this.paramCollector != null && !this.paramCollector.typeParameters.isEmpty()) { + // Proguard also strips the Signature attribute, so we have to reconstruct that, to a point where this class is accepted by + // javac when on the classpath. This requires every type parameter referenced to have been declared within the class. + // Records are implicitly static and have a defined superclass of java/lang/Record, so there can be type parameters in play from: + // - fields + // - methods (which can declare their own formal parameters) + // - record components + // - superinterfaces (less important, we just get raw type warnings) + // + // This will not be perfect, but provides enough information to allow compilation and enhance decompiler output. + // todo: allow type-specific rules to infer deeper levels (for example, T with raw type Comparable is probably Comparable) + + final SignatureWriter sw = new SignatureWriter(); + // Formal parameters + // find all used type parameters, plus guesstimated bounds + for (final Map.Entry param : this.paramCollector.typeParameters.entrySet()) { + sw.visitFormalTypeParameter(param.getKey()); + if (!param.getValue().equals(TypeParameterCollector.UNKNOWN)) { + final Inheritance.IClassInfo cls = Action.this.inh.getClass(param.getValue()).orElse(null); + if (cls != null) { + final SignatureVisitor parent; + if ((cls.getAccess() & Opcodes.ACC_INTERFACE) != 0) { + parent = sw.visitInterfaceBound(); + } else { + parent = sw.visitClassBound(); + } + parent.visitClassType(param.getValue()); + parent.visitEnd(); + continue; + } else { + Action.this.debug.accept("Unable to find information for type " + param.getValue()); + } + } + final SignatureVisitor cls = sw.visitClassBound(); + cls.visitClassType("java/lang/Object"); + cls.visitEnd(); + } + + // Supertype (always Record) + final SignatureVisitor sv = sw.visitSuperclass(); + sv.visitClassType(this.node.superName); + sv.visitEnd(); + + // Superinterfaces + for (final String superI : this.node.interfaces) { + final SignatureVisitor itfV = sw.visitInterface(); + itfV.visitClassType(superI); + sv.visitEnd(); + } + final String newSignature = sw.toString(); + Action.this.debug.accept("New signature for " + this.node.name + ": " + newSignature); + this.node.signature = newSignature; + } + // feed node through to the original output visitor + if (this.node != null && this.originalParent != null) { + this.node.accept(this.originalParent); + } + } + } + } + + static class TypeParameterCollector extends SignatureVisitor { + private static final int RETURN_TYPE = -2; + static final int FIELD = -1; + static final String UNKNOWN = "???"; + Map typeParameters = new HashMap<>(); // + Type baseType; + int param = -1; + int level; + Set declaredParams; + + public TypeParameterCollector() { + super(ASM.API); + } + + @Override + public void visitFormalTypeParameter(final String name) { + if (this.declaredParams == null) + this.declaredParams = new HashSet<>(); + this.declaredParams.add(name); + } + + @Override + public void visitTypeVariable(final String name) { + if ((!this.typeParameters.containsKey(name) || this.typeParameters.get(name).equals(TypeParameterCollector.UNKNOWN)) && (this.declaredParams == null || !this.declaredParams.contains(name))) { + if (this.level == 0 && this.baseType != null) { + final String typeName; + switch (this.param) { + case TypeParameterCollector.FIELD: // field + typeName = this.baseType.getInternalName(); + break; + case TypeParameterCollector.RETURN_TYPE: // method return value + typeName = this.baseType.getReturnType().getInternalName(); + break; + default: + typeName = this.baseType.getArgumentTypes()[this.param].getInternalName(); + break; + } + this.typeParameters.put(name, typeName); + } else { + this.typeParameters.put(name, TypeParameterCollector.UNKNOWN); + } + } + super.visitTypeVariable(name); + } + + @Override + public void visitClassType(final String name) { + this.level++; + super.visitClassType(name); + } + + @Override + public void visitInnerClassType(final String name) { + this.level++; + super.visitInnerClassType(name); + } + + @Override + public SignatureVisitor visitTypeArgument(final char wildcard) { + this.level++; + return super.visitTypeArgument(wildcard); + } + + @Override + public void visitEnd() { + if (this.level-- <= 0) { + throw new IllegalStateException("Unbalanced signature levels"); + } + super.visitEnd(); + } + + // for methods + + @Override + public SignatureVisitor visitParameterType() { + this.param++; + return super.visitParameterType(); + } + + @Override + public SignatureVisitor visitReturnType() { + this.param = TypeParameterCollector.RETURN_TYPE; + return super.visitReturnType(); + } + } + +} diff --git a/subprojects/gradle-plugin/src/main/java/org/spongepowered/gradle/vanilla/internal/transformer/Transformers.java b/subprojects/forgeautorenamingtool-transformers/src/main/java/org/spongepowered/gradle/vanilla/renamer/transformer/Transformers.java similarity index 85% rename from subprojects/gradle-plugin/src/main/java/org/spongepowered/gradle/vanilla/internal/transformer/Transformers.java rename to subprojects/forgeautorenamingtool-transformers/src/main/java/org/spongepowered/gradle/vanilla/renamer/transformer/Transformers.java index 4cc6cc0..ba56c4e 100644 --- a/subprojects/gradle-plugin/src/main/java/org/spongepowered/gradle/vanilla/internal/transformer/Transformers.java +++ b/subprojects/forgeautorenamingtool-transformers/src/main/java/org/spongepowered/gradle/vanilla/renamer/transformer/Transformers.java @@ -22,7 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package org.spongepowered.gradle.vanilla.internal.transformer; +package org.spongepowered.gradle.vanilla.renamer.transformer; import net.minecraftforge.fart.api.Transformer; @@ -34,15 +34,15 @@ private Transformers() { } public static Transformer filterEntries(final Set allowedPackages) { - return new FilterClassesTransformer(allowedPackages); + return new FilterClassesTransformer.Action(allowedPackages); } public static Transformer fixLvNames() { - return new LocalVariableNameFixer(); + return LocalVariableNameFixer.Action.INSTANCE; } public static Transformer.Factory recordSignatureFixer() { - return ctx -> new RecordSignatureFixer(ctx.getDebug(), ctx.getInheritance()); + return ctx -> new RecordSignatureFixer.Action(ctx.getDebug(), ctx.getInheritance()); } } diff --git a/subprojects/gradle-plugin/src/main/java/org/spongepowered/gradle/vanilla/internal/asm/VariableScope.java b/subprojects/forgeautorenamingtool-transformers/src/main/java/org/spongepowered/gradle/vanilla/renamer/transformer/VariableScope.java similarity index 98% rename from subprojects/gradle-plugin/src/main/java/org/spongepowered/gradle/vanilla/internal/asm/VariableScope.java rename to subprojects/forgeautorenamingtool-transformers/src/main/java/org/spongepowered/gradle/vanilla/renamer/transformer/VariableScope.java index c919757..613b6a4 100644 --- a/subprojects/gradle-plugin/src/main/java/org/spongepowered/gradle/vanilla/internal/asm/VariableScope.java +++ b/subprojects/forgeautorenamingtool-transformers/src/main/java/org/spongepowered/gradle/vanilla/renamer/transformer/VariableScope.java @@ -22,7 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package org.spongepowered.gradle.vanilla.internal.asm; +package org.spongepowered.gradle.vanilla.renamer.transformer; import org.checkerframework.checker.nullness.qual.Nullable; diff --git a/subprojects/gradle-plugin/src/main/java/org/spongepowered/gradle/vanilla/internal/asm/VariableScopeTracker.java b/subprojects/forgeautorenamingtool-transformers/src/main/java/org/spongepowered/gradle/vanilla/renamer/transformer/VariableScopeTracker.java similarity index 97% rename from subprojects/gradle-plugin/src/main/java/org/spongepowered/gradle/vanilla/internal/asm/VariableScopeTracker.java rename to subprojects/forgeautorenamingtool-transformers/src/main/java/org/spongepowered/gradle/vanilla/renamer/transformer/VariableScopeTracker.java index 5791311..7324df2 100644 --- a/subprojects/gradle-plugin/src/main/java/org/spongepowered/gradle/vanilla/internal/asm/VariableScopeTracker.java +++ b/subprojects/forgeautorenamingtool-transformers/src/main/java/org/spongepowered/gradle/vanilla/renamer/transformer/VariableScopeTracker.java @@ -22,7 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package org.spongepowered.gradle.vanilla.internal.asm; +package org.spongepowered.gradle.vanilla.renamer.transformer; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; diff --git a/subprojects/gradle-plugin/build.gradle.kts b/subprojects/gradle-plugin/build.gradle.kts index 810742a..2e6fb0d 100644 --- a/subprojects/gradle-plugin/build.gradle.kts +++ b/subprojects/gradle-plugin/build.gradle.kts @@ -1,4 +1,3 @@ -import org.gradle.kotlin.dsl.support.serviceOf import org.jetbrains.gradle.ext.TaskTriggersConfig plugins { @@ -16,9 +15,6 @@ val jarMerge by sourceSets.creating { val jarDecompile by sourceSets.creating { configurations.named(this.implementationConfigurationName) { extendsFrom(commonDeps) } } -val accessWiden by sourceSets.creating { - configurations.named(this.implementationConfigurationName) { extendsFrom(commonDeps) } -} val shadow by sourceSets.creating { configurations.named(this.implementationConfigurationName) { extendsFrom(commonDeps) } } @@ -27,23 +23,13 @@ configurations { api { extendsFrom(commonDeps) } } -val accessWidenerVersion: String by project -val asmVersion: String by project -val checkerVersion: String by project -val forgeFlowerVersion: String by project -val forgeAutoRenamingToolVersion: String by project -val junitVersion: String by project -val mergeToolVersion: String by project dependencies { // All source sets commonDeps(gradleApi()) commonDeps(libs.asm) commonDeps(libs.asm.commons) commonDeps(libs.asm.util) - commonDeps(libs.forgeAutoRenamingTool) { - exclude("org.ow2.asm") // Use our own ASM - exclude("net.sf.jopt-simple") - } + commonDeps(libs.srgUtils) // Just main implementation(libs.gson) @@ -69,12 +55,6 @@ dependencies { "jarDecompileCompileOnly"(libs.forgeFlower) implementation(jarDecompile.output) - // Access widener worker (match with Constants) - "accessWidenCompileOnly"(libs.accessWidener) { - exclude("org.ow2.asm") - } - implementation(accessWiden.output) - "shadowCompileOnly"(libs.shadowPlugin) implementation(shadow.output) @@ -118,7 +98,6 @@ tasks { jar { from(jarMerge.output) from(jarDecompile.output) - from(accessWiden.output) from(shadow.output) } diff --git a/subprojects/gradle-plugin/src/accessWiden/java/org/spongepowered/gradle/vanilla/internal/worker/AccessWidenerTransformerProvider.java b/subprojects/gradle-plugin/src/accessWiden/java/org/spongepowered/gradle/vanilla/internal/worker/AccessWidenerTransformerProvider.java deleted file mode 100644 index 39a840e..0000000 --- a/subprojects/gradle-plugin/src/accessWiden/java/org/spongepowered/gradle/vanilla/internal/worker/AccessWidenerTransformerProvider.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * This file is part of VanillaGradle, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * 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 org.spongepowered.gradle.vanilla.internal.worker; - -import net.fabricmc.accesswidener.AccessWidener; -import net.fabricmc.accesswidener.AccessWidenerReader; -import net.minecraftforge.fart.api.Transformer; -import org.gradle.api.GradleException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.BufferedReader; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Set; -import java.util.function.Function; - -public final class AccessWidenerTransformerProvider implements Function, Transformer> { - - private static final Logger LOGGER = LoggerFactory.getLogger(AccessWidenerTransformerProvider.class); - - @Override - public Transformer apply(final Set paths) { - final AccessWidener widener = new AccessWidener(); - final AccessWidenerReader reader = new AccessWidenerReader(widener); - - for (final Path widenerFile : paths) { - try (final BufferedReader fileReader = Files.newBufferedReader(widenerFile, StandardCharsets.UTF_8)) { - reader.read(fileReader); - } catch (final IOException ex) { - AccessWidenerTransformerProvider.LOGGER.error("Failed to read access widener from file {}", widenerFile, ex); - throw new GradleException("Access widening failed"); - } - } - - return new AccessWidenerEntryTransformer(widener); - } -} diff --git a/subprojects/gradle-plugin/src/jarDecompile/java/org/spongepowered/gradle/vanilla/internal/worker/LineMappingVisitor.java b/subprojects/gradle-plugin/src/jarDecompile/java/org/spongepowered/gradle/vanilla/internal/worker/LineMappingVisitor.java index a9384f9..d2d9381 100644 --- a/subprojects/gradle-plugin/src/jarDecompile/java/org/spongepowered/gradle/vanilla/internal/worker/LineMappingVisitor.java +++ b/subprojects/gradle-plugin/src/jarDecompile/java/org/spongepowered/gradle/vanilla/internal/worker/LineMappingVisitor.java @@ -61,7 +61,7 @@ static class MethodLineFixer extends MethodVisitor { public void visitLineNumber(final int line, final Label start) { Integer mapped = this.lineMapping.get(line); if (mapped == null) { - final Map.Entry entry = this.lineMapping.higherEntry(line); + final Map.Entry entry = this.lineMapping.ceilingEntry(line); if (entry != null) { mapped = entry.getValue(); } diff --git a/subprojects/gradle-plugin/src/main/java/org/spongepowered/gradle/vanilla/internal/repository/modifier/ArtifactModifier.java b/subprojects/gradle-plugin/src/main/java/org/spongepowered/gradle/vanilla/internal/repository/modifier/ArtifactModifier.java index cb41ffc..de8b666 100644 --- a/subprojects/gradle-plugin/src/main/java/org/spongepowered/gradle/vanilla/internal/repository/modifier/ArtifactModifier.java +++ b/subprojects/gradle-plugin/src/main/java/org/spongepowered/gradle/vanilla/internal/repository/modifier/ArtifactModifier.java @@ -24,8 +24,6 @@ */ package org.spongepowered.gradle.vanilla.internal.repository.modifier; -import net.minecraftforge.fart.api.Renamer; -import net.minecraftforge.fart.api.Transformer; import org.spongepowered.gradle.vanilla.repository.MinecraftResolver; import java.io.IOException; @@ -33,7 +31,7 @@ import java.util.concurrent.CompletableFuture; /** - * Some sort of operation that can be performed on a jar, via a {@link Renamer}. + * Some sort of operation that can be performed on a jar, via a {@code Renamer}. */ public interface ArtifactModifier { diff --git a/subprojects/gradle-plugin/src/main/java/org/spongepowered/gradle/vanilla/internal/transformer/FilterClassesTransformer.java b/subprojects/gradle-plugin/src/main/java/org/spongepowered/gradle/vanilla/internal/transformer/FilterClassesTransformer.java deleted file mode 100644 index b2b614e..0000000 --- a/subprojects/gradle-plugin/src/main/java/org/spongepowered/gradle/vanilla/internal/transformer/FilterClassesTransformer.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of VanillaGradle, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * 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 org.spongepowered.gradle.vanilla.internal.transformer; - -import net.minecraftforge.fart.api.Transformer; - -import java.util.Set; - -final class FilterClassesTransformer implements Transformer { - - private final String[] allowedPrefixes; - - FilterClassesTransformer(final Set allowedPrefixes) { - this.allowedPrefixes = allowedPrefixes.toArray(new String[0]); - } - - @Override - public ClassEntry process(final ClassEntry entry) { - if (this.matches(entry.getName())) { - return entry; - } else { - return null; - } - } - - private boolean matches(final String className) { - if (!className.contains("/")) { - return true; - } - - for (final String pkg : this.allowedPrefixes) { - if (className.startsWith(pkg)) { - return true; - } - } - return false; - - } - - @Override - public ResourceEntry process(final ResourceEntry entry) { - return entry; - /*if (!this.matches(entry.getConfig().getService().replace('.', '/'))) { - return null; - } - - final List providers = new ArrayList<>(entry.getConfig().getProviders().size()); - for (final String provider : entry.getConfig().getProviders()) { - if (this.matches(provider.replace('.', '/'))) { - providers.add(provider); - } - } - if (providers.isEmpty()) { - return null; - } - - return new JarServiceProviderConfigurationEntry(entry.getTime(), new ServiceProviderConfiguration(entry.getConfig().getService(), providers));*/ - } -} diff --git a/subprojects/gradle-plugin/src/main/java/org/spongepowered/gradle/vanilla/internal/transformer/RecordSignatureFixer.java b/subprojects/gradle-plugin/src/main/java/org/spongepowered/gradle/vanilla/internal/transformer/RecordSignatureFixer.java deleted file mode 100644 index 6bcba9a..0000000 --- a/subprojects/gradle-plugin/src/main/java/org/spongepowered/gradle/vanilla/internal/transformer/RecordSignatureFixer.java +++ /dev/null @@ -1,280 +0,0 @@ -/* - * This file is part of VanillaGradle, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * 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 org.spongepowered.gradle.vanilla.internal.transformer; - -import net.minecraftforge.fart.api.Inheritance; -import net.minecraftforge.fart.api.Transformer; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.FieldVisitor; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.RecordComponentVisitor; -import org.objectweb.asm.Type; -import org.objectweb.asm.signature.SignatureReader; -import org.objectweb.asm.signature.SignatureVisitor; -import org.objectweb.asm.signature.SignatureWriter; -import org.objectweb.asm.tree.ClassNode; -import org.spongepowered.gradle.vanilla.internal.Constants; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.function.Consumer; - -public class RecordSignatureFixer implements Transformer { - private final Consumer debug; - private final Inheritance inh; - - public RecordSignatureFixer(final Consumer debug, final Inheritance inh) { - this.debug = debug; - this.inh = inh; - } - - @Override - public ClassEntry process(final ClassEntry entry) { - final ClassReader reader = new ClassReader(entry.getData()); - final ClassWriter writer = new ClassWriter(reader, 0); - - reader.accept(new Fixer(writer), 0); - return ClassEntry.create(entry.getName(), entry.getTime(), writer.toByteArray()); - } - - private class Fixer extends ClassVisitor { - private TypeParameterCollector paramCollector; - private boolean isRecord; - private boolean patchSignature; - private final ClassVisitor originalParent; - private ClassNode node; - - public Fixer(final ClassVisitor parent) { - super(Constants.ASM_VERSION, parent); - this.originalParent = parent; - } - - @Override - public void visit(final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) { - this.isRecord = "java/lang/Record".equals(superName); - this.patchSignature = signature == null; - if (this.isRecord && this.patchSignature) { - this.node = new ClassNode(); - this.cv = this.node; - } - // todo: validate type parameters from superinterfaces - // this would need to get signature information from bytecode + runtime classes - super.visit(version, access, name, signature, superName, interfaces); - } - - @Override - public RecordComponentVisitor visitRecordComponent(final String name, final String descriptor, final String signature) { - if (signature != null && this.patchSignature) { // signature implies non-primitive type - if (this.paramCollector == null) { - this.paramCollector = new TypeParameterCollector(); - } - this.paramCollector.baseType = Type.getType(descriptor); - this.paramCollector.param = TypeParameterCollector.FIELD; - new SignatureReader(signature).accept(this.paramCollector); - } - return super.visitRecordComponent(name, descriptor, signature); - } - - @Override - public FieldVisitor visitField(final int access, final String name, final String descriptor, final String signature, final Object value) { - if (this.isRecord && signature != null && this.patchSignature) { // signature implies non-primitive type - if (this.paramCollector == null) - this.paramCollector = new TypeParameterCollector(); - this.paramCollector.baseType = Type.getType(descriptor); - this.paramCollector.param = TypeParameterCollector.FIELD; - new SignatureReader(signature).accept(this.paramCollector); - } - return super.visitField(access, name, descriptor, signature, value); - } - - @Override - public MethodVisitor visitMethod(final int access, final String name, final String descriptor, final String signature, final String[] exceptions) { - if ((access & Opcodes.ACC_STATIC) == 0 && this.isRecord && signature != null && this.patchSignature) { // signature implies non-primitive type - if (this.paramCollector == null) - this.paramCollector = new TypeParameterCollector(); - this.paramCollector.baseType = Type.getType(descriptor); - this.paramCollector.param = TypeParameterCollector.FIELD; // start out before parameters come in - new SignatureReader(signature).accept(this.paramCollector); - if (this.paramCollector.declaredParams != null) { - this.paramCollector.declaredParams.clear(); - } - } - return super.visitMethod(access, name, descriptor, signature, exceptions); - } - - @Override - public void visitEnd() { - if (this.isRecord && this.patchSignature && this.paramCollector != null && !this.paramCollector.typeParameters.isEmpty()) { - // Proguard also strips the Signature attribute, so we have to reconstruct that, to a point where this class is accepted by - // javac when on the classpath. This requires every type parameter referenced to have been declared within the class. - // Records are implicitly static and have a defined superclass of java/lang/Record, so there can be type parameters in play from: - // - fields - // - methods (which can declare their own formal parameters) - // - record components - // - superinterfaces (less important, we just get raw type warnings) - // - // This will not be perfect, but provides enough information to allow compilation and enhance decompiler output. - // todo: allow type-specific rules to infer deeper levels (for example, T with raw type Comparable is probably Comparable) - - final SignatureWriter sw = new SignatureWriter(); - // Formal parameters - // find all used type parameters, plus guesstimated bounds - for (final Map.Entry param : this.paramCollector.typeParameters.entrySet()) { - sw.visitFormalTypeParameter(param.getKey()); - if (!param.getValue().equals(TypeParameterCollector.UNKNOWN)) { - final Inheritance.IClassInfo cls = RecordSignatureFixer.this.inh.getClass(param.getValue()).orElse(null); - if (cls != null) { - final SignatureVisitor parent; - if ((cls.getAccess() & Opcodes.ACC_INTERFACE) != 0) { - parent = sw.visitInterfaceBound(); - } else { - parent = sw.visitClassBound(); - } - parent.visitClassType(param.getValue()); - parent.visitEnd(); - continue; - } else { - RecordSignatureFixer.this.debug.accept("Unable to find information for type " + param.getValue()); - } - } - final SignatureVisitor cls = sw.visitClassBound(); - cls.visitClassType("java/lang/Object"); - cls.visitEnd(); - } - - // Supertype (always Record) - final SignatureVisitor sv = sw.visitSuperclass(); - sv.visitClassType(this.node.superName); - sv.visitEnd(); - - // Superinterfaces - for (final String superI : this.node.interfaces) { - final SignatureVisitor itfV = sw.visitInterface(); - itfV.visitClassType(superI); - sv.visitEnd(); - } - final String newSignature = sw.toString(); - RecordSignatureFixer.this.debug.accept("New signature for " + this.node.name + ": " + newSignature); - this.node.signature = newSignature; - } - // feed node through to the original output visitor - if (this.node != null && this.originalParent != null) { - this.node.accept(this.originalParent); - } - } - } - - static class TypeParameterCollector extends SignatureVisitor { - private static final int RETURN_TYPE = -2; - static final int FIELD = -1; - static final String UNKNOWN = "???"; - Map typeParameters = new HashMap<>(); // - Type baseType; - int param = -1; - int level; - Set declaredParams; - - public TypeParameterCollector() { - super(Constants.ASM_VERSION); - } - - @Override - public void visitFormalTypeParameter(final String name) { - if (this.declaredParams == null) - this.declaredParams = new HashSet<>(); - this.declaredParams.add(name); - } - - @Override - public void visitTypeVariable(final String name) { - if ((!this.typeParameters.containsKey(name) || this.typeParameters.get(name).equals(TypeParameterCollector.UNKNOWN)) && (this.declaredParams == null || !this.declaredParams.contains(name))) { - if (this.level == 0 && this.baseType != null) { - final String typeName; - switch (this.param) { - case TypeParameterCollector.FIELD: // field - typeName = this.baseType.getInternalName(); - break; - case TypeParameterCollector.RETURN_TYPE: // method return value - typeName = this.baseType.getReturnType().getInternalName(); - break; - default: - typeName = this.baseType.getArgumentTypes()[this.param].getInternalName(); - break; - } - this.typeParameters.put(name, typeName); - } else { - this.typeParameters.put(name, TypeParameterCollector.UNKNOWN); - } - } - super.visitTypeVariable(name); - } - - @Override - public void visitClassType(final String name) { - this.level++; - super.visitClassType(name); - } - - @Override - public void visitInnerClassType(final String name) { - this.level++; - super.visitInnerClassType(name); - } - - @Override - public SignatureVisitor visitTypeArgument(final char wildcard) { - this.level++; - return super.visitTypeArgument(wildcard); - } - - @Override - public void visitEnd() { - if (this.level-- <= 0) { - throw new IllegalStateException("Unbalanced signature levels"); - } - super.visitEnd(); - } - - // for methods - - @Override - public SignatureVisitor visitParameterType() { - this.param++; - return super.visitParameterType(); - } - - @Override - public SignatureVisitor visitReturnType() { - this.param = TypeParameterCollector.RETURN_TYPE; - return super.visitReturnType(); - } - } - -} diff --git a/subprojects/plugin-remapper/build.gradle.kts b/subprojects/plugin-remapper/build.gradle.kts new file mode 100644 index 0000000..22bf694 --- /dev/null +++ b/subprojects/plugin-remapper/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + alias(libs.plugins.gradlePluginPublish) + alias(libs.plugins.indra.publishing.gradlePlugin) +} + +dependencies { + compileOnly(project(":vanillagradle-forgeautorenamingtool-spi")) + api(libs.mammoth) +} + +indraPluginPublishing { + website("https://spongepowered.org") + bundleTags(listOf("remapping", "forge")) + plugin( + /* id = */ "gradle.remapper", + /* mainClass = */ "org.spongepowered.gradle.vanilla.remap.RemapPlugin", + /* displayName = */ "VanillaGradle Remapping", + /* description = */ "Integrate ForgeAutoRenamingTool (and eventually source remapping) with the Gradle pipeline" + ) +} diff --git a/subprojects/plugin-remapper/src/main/java/org/spongepowered/gradle/vanilla/remap/RemapPlugin.java b/subprojects/plugin-remapper/src/main/java/org/spongepowered/gradle/vanilla/remap/RemapPlugin.java new file mode 100644 index 0000000..0021b49 --- /dev/null +++ b/subprojects/plugin-remapper/src/main/java/org/spongepowered/gradle/vanilla/remap/RemapPlugin.java @@ -0,0 +1,30 @@ +package org.spongepowered.gradle.vanilla.remap; + +import net.kyori.mammoth.ProjectPlugin; +import org.gradle.api.Project; +import org.gradle.api.plugins.ExtensionContainer; +import org.gradle.api.plugins.PluginContainer; +import org.gradle.api.tasks.TaskContainer; +import org.jetbrains.annotations.NotNull; + +public class RemapPlugin implements ProjectPlugin { + + @Override + public void apply( + final @NotNull Project project, + final @NotNull PluginContainer plugins, + final @NotNull ExtensionContainer extensions, + final @NotNull TaskContainer tasks + ) { + // add extension + // OUTGOING + // extension can create remapped variants of any existing outgoing configuration + // todo: add these to an existing software component? + + // INCOMING + // an artifact transform? + + // DURING RESOLUTION + // ad-hoc create classpath + argument additions + } +} diff --git a/subprojects/plugin-remapper/src/main/java/org/spongepowered/gradle/vanilla/remap/RemapStep.java b/subprojects/plugin-remapper/src/main/java/org/spongepowered/gradle/vanilla/remap/RemapStep.java new file mode 100644 index 0000000..9238fbb --- /dev/null +++ b/subprojects/plugin-remapper/src/main/java/org/spongepowered/gradle/vanilla/remap/RemapStep.java @@ -0,0 +1,40 @@ +package org.spongepowered.gradle.vanilla.remap; + +import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.provider.ListProperty; +import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.Nested; +import org.gradle.process.CommandLineArgumentProvider; + +/** + * A transformation to be run as part of the binary remap step. + * + * @since 0.2.1 + */ +public interface RemapStep { + + /** + * Classpath elements to add to the remapper classpath. + * + * @return the classpath file collection + * @since 0.2.1 + */ + @InputFiles + ConfigurableFileCollection getClasspath(); + + /** + * Get a list of argument providers to contribute to the remapper + * command line. + * + * @return the argument providers + * @since 0.2.1 + */ + @Nested + ListProperty getArgumentProviders(); + + + interface ForgeAutoRenamingToolStep extends RemapStep { + + } + +} diff --git a/subprojects/plugin-remapper/src/main/java/org/spongepowered/gradle/vanilla/remap/task/RemapJar.java b/subprojects/plugin-remapper/src/main/java/org/spongepowered/gradle/vanilla/remap/task/RemapJar.java new file mode 100644 index 0000000..b06e238 --- /dev/null +++ b/subprojects/plugin-remapper/src/main/java/org/spongepowered/gradle/vanilla/remap/task/RemapJar.java @@ -0,0 +1,92 @@ +package org.spongepowered.gradle.vanilla.remap.task; + +import org.gradle.api.GradleException; +import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.provider.ListProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.Nested; +import org.gradle.api.tasks.bundling.Jar; +import org.gradle.process.CommandLineArgumentProvider; +import org.gradle.workers.WorkAction; +import org.gradle.workers.WorkParameters; +import org.gradle.workers.WorkerExecutor; + +import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +/** + * Produce a remapped artifact + */ +public abstract class RemapJar extends Jar { + + @InputFiles + public abstract ConfigurableFileCollection getToolClasspath(); + + @Nested + public abstract ListProperty getToolArguments(); + + @Input + public abstract Property getToolMainClass(); + + @Inject + protected abstract WorkerExecutor getWorkerExecutor(); + + @Override + protected void copy() { + super.copy(); + + // todo: use this somewhere?? + final File output = this.getArchiveFile().get().getAsFile(); + + // Post-process?? + this.getWorkerExecutor().classLoaderIsolation(spec -> { + spec.getClasspath().from(this.getToolClasspath()); + }).submit(RemapWorkAction.class, params -> { + params.getMainClass().set(this.getToolMainClass()); + params.getArguments().set(this.getToolArguments().map(args -> { + final List ret = new ArrayList<>(); + for (final CommandLineArgumentProvider argumentProvider : args) { + for (final String arg : argumentProvider.asArguments()) { + ret.add(arg); + } + } + return ret; + })); + }); + this.getWorkerExecutor().await(); + } + + + static abstract class RemapWorkAction implements WorkAction { + interface Params extends WorkParameters { + ListProperty getArguments(); + Property getMainClass(); + } + + /** + * The work to perform when this work item executes. + */ + @Override + public void execute() { + try { + Class.forName(this.getParameters().getMainClass().get()) + .getMethod("main", String[].class) + .invoke(null, (Object[]) this.getParameters().getArguments().get().toArray(new String[0])); + } catch (final IllegalAccessException | ClassNotFoundException | NoSuchMethodException ex) { + throw new GradleException("Failed to invoke tool main class", ex); + } catch (final InvocationTargetException ex) { + if (ex.getCause() instanceof RuntimeException) { + throw (RuntimeException) ex.getCause(); + } else { + throw new GradleException("Failed during tool action", ex.getCause()); + } + } + } + } +}