diff options
Diffstat (limited to 'src/main/java/net/fabricmc/loom')
99 files changed, 7902 insertions, 304 deletions
diff --git a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java index 060a094b..85855067 100644 --- a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java +++ b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java @@ -42,6 +42,13 @@ import net.fabricmc.loom.configuration.LoomDependencyManager; import net.fabricmc.loom.configuration.accesswidener.AccessWidenerFile; import net.fabricmc.loom.configuration.processors.JarProcessorManager; import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl; +import net.fabricmc.loom.configuration.providers.forge.FieldMigratedMappingsProvider; +import net.fabricmc.loom.configuration.providers.forge.ForgeProvider; +import net.fabricmc.loom.configuration.providers.forge.ForgeUniversalProvider; +import net.fabricmc.loom.configuration.providers.forge.ForgeUserdevProvider; +import net.fabricmc.loom.configuration.providers.forge.McpConfigProvider; +import net.fabricmc.loom.configuration.providers.forge.PatchProvider; +import net.fabricmc.loom.configuration.providers.forge.SrgProvider; import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider; import net.fabricmc.loom.extension.LoomFiles; @@ -81,7 +88,7 @@ public interface LoomGradleExtension extends LoomGradleExtensionAPI { } default MappingsProviderImpl getMappingsProvider() { - return getDependencyManager().getProvider(MappingsProviderImpl.class); + return getDependencyManager().getProvider(isForge() ? FieldMigratedMappingsProvider.class : MappingsProviderImpl.class); } default MinecraftMappedProvider getMinecraftMappedProvider() { @@ -108,4 +115,45 @@ public interface LoomGradleExtension extends LoomGradleExtensionAPI { List<AccessWidenerFile> getTransitiveAccessWideners(); void addTransitiveAccessWideners(List<AccessWidenerFile> accessWidenerFiles); + + // =================== + // Architectury Loom + // =================== + default PatchProvider getPatchProvider() { + return getDependencyManager().getProvider(PatchProvider.class); + } + + default McpConfigProvider getMcpConfigProvider() { + return getDependencyManager().getProvider(McpConfigProvider.class); + } + + default boolean isDataGenEnabled() { + return isForge() && !getForge().getDataGenMods().isEmpty(); + } + + default boolean isForgeAndOfficial() { + return isForge() && getMcpConfigProvider().isOfficial(); + } + + default boolean isForgeAndNotOfficial() { + return isForge() && !getMcpConfigProvider().isOfficial(); + } + + boolean supportsInclude(); + + default SrgProvider getSrgProvider() { + return getDependencyManager().getProvider(SrgProvider.class); + } + + default ForgeUniversalProvider getForgeUniversalProvider() { + return getDependencyManager().getProvider(ForgeUniversalProvider.class); + } + + default ForgeUserdevProvider getForgeUserdevProvider() { + return getDependencyManager().getProvider(ForgeUserdevProvider.class); + } + + default ForgeProvider getForgeProvider() { + return getDependencyManager().getProvider(ForgeProvider.class); + } } diff --git a/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java b/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java index 63e6e871..bdc94f3a 100644 --- a/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java +++ b/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java @@ -25,6 +25,9 @@ package net.fabricmc.loom; import java.util.Objects; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; @@ -62,7 +65,13 @@ public class LoomGradlePlugin implements BootstrappedPlugin { } public void apply(Project project) { - project.getLogger().lifecycle("Fabric Loom: " + LOOM_VERSION); + Set<String> loggedVersions = new HashSet<>(Arrays.asList(System.getProperty("loom.printed.logged", "").split(","))); + + if (!loggedVersions.contains(LOOM_VERSION)) { + loggedVersions.add(LOOM_VERSION); + System.setProperty("loom.printed.logged", String.join(",", loggedVersions)); + project.getLogger().lifecycle("Architectury Loom: " + LOOM_VERSION); + } refreshDeps = project.getGradle().getStartParameter().isRefreshDependencies() || Boolean.getBoolean("loom.refresh"); diff --git a/src/main/java/net/fabricmc/loom/LoomRepositoryPlugin.java b/src/main/java/net/fabricmc/loom/LoomRepositoryPlugin.java index 48b61e6d..28045071 100644 --- a/src/main/java/net/fabricmc/loom/LoomRepositoryPlugin.java +++ b/src/main/java/net/fabricmc/loom/LoomRepositoryPlugin.java @@ -64,6 +64,13 @@ public class LoomRepositoryPlugin implements Plugin<PluginAware> { repo.setUrl(files.getRemappedModCache()); }); repositories.maven(repo -> { + repo.setName("Architectury"); + repo.setUrl("https://maven.architectury.dev/"); + repo.mavenContent(content -> { + content.includeGroup("dev.architectury"); + }); + }); + repositories.maven(repo -> { repo.setName("Fabric"); repo.setUrl(MirrorUtil.getFabricRepository(target)); }); @@ -71,6 +78,18 @@ public class LoomRepositoryPlugin implements Plugin<PluginAware> { repo.setName("Mojang"); repo.setUrl(MirrorUtil.getLibrariesBase(target)); }); + repositories.maven(repo -> { + repo.setName("Forge"); + repo.setUrl("https://maven.minecraftforge.net/"); + + repo.content(descriptor -> { + descriptor.excludeGroupByRegex("org\\.eclipse\\.?.*"); + }); + repo.metadataSources(sources -> { + sources.mavenPom(); + sources.ignoreGradleMetadataRedirection(); + }); + }); repositories.mavenCentral(); repositories.ivy(repo -> { diff --git a/src/main/java/net/fabricmc/loom/api/ForgeExtensionAPI.java b/src/main/java/net/fabricmc/loom/api/ForgeExtensionAPI.java new file mode 100644 index 00000000..e455d742 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/api/ForgeExtensionAPI.java @@ -0,0 +1,151 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.api; + +import java.util.List; + +import org.gradle.api.Action; +import org.gradle.api.NamedDomainObjectContainer; +import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.provider.Property; +import org.gradle.api.provider.SetProperty; +import org.jetbrains.annotations.ApiStatus; + +/** + * This is the forge extension api available exposed to build scripts. + */ +@ApiStatus.NonExtendable +public interface ForgeExtensionAPI { + /** + * If true, {@linkplain LoomGradleExtensionAPI#getAccessWidenerPath() the project access widener file} + * will be remapped to an access transformer file if set. + * + * @return the property + */ + Property<Boolean> getConvertAccessWideners(); + + /** + * A set of additional access widener files that will be converted to access transformers + * {@linkplain #getConvertAccessWideners() if enabled}. The files are specified as paths in jar files + * (e.g. {@code path/to/my_aw.accesswidener}). + * + * @return the property + */ + SetProperty<String> getExtraAccessWideners(); + + /** + * A collection of all project access transformers. + * The collection should only contain AT files, and not directories or other files. + * + * <p>If this collection is empty, Loom tries to resolve the AT from the default path + * ({@code META-INF/accesstransformer.cfg} in the {@code main} source set). + * + * @return the collection of AT files + */ + ConfigurableFileCollection getAccessTransformers(); + + /** + * Adds a {@linkplain #getAccessTransformers() project access transformer}. + * + * @param file the file, evaluated as per {@link org.gradle.api.Project#file(Object)} + */ + void accessTransformer(Object file); + + /** + * A set of all mixin configs related to source set resource roots. + * All mixin configs must be added to this property so that they apply in a dev environment. + * + * @return the property + */ + SetProperty<String> getMixinConfigs(); + + /** + * Adds mixin config files to {@link #getMixinConfigs() mixinConfigs}. + * + * @param mixinConfigs the mixin config file paths relative to resource roots + */ + void mixinConfigs(String... mixinConfigs); + + /** + * Adds mixin config files to {@link #getMixinConfigs() mixinConfigs}. + * + * @param mixinConfigs the mixin config file paths relative to resource roots + */ + default void mixinConfig(String... mixinConfigs) { + mixinConfigs(mixinConfigs); + } + + /** + * If true, upstream Mixin from Sponge will be replaced with Fabric's or Architectury's fork. + * This is enabled by default. + * + * @return the property + */ + Property<Boolean> getUseCustomMixin(); + + /** + * A list of mod IDs for mods applied for data generation. + * The returned list is unmodifiable but not immutable - it will reflect changes done with + * {@link #dataGen(Action)}. + * + * @return the list + */ + List<String> getDataGenMods(); + + /** + * Applies data generation settings. + * + * @param action the action to configure data generation + */ + void dataGen(Action<DataGenConsumer> action); + + /** + * Data generation config. + */ + @ApiStatus.NonExtendable + interface DataGenConsumer { + /** + * Adds mod IDs applied for data generation. + * + * @param modIds the mod IDs + */ + void mod(String... modIds); + } + + /** + * Configures local mods. + * + * @param action the configuration action + */ + void localMods(Action<NamedDomainObjectContainer<ForgeLocalMod>> action); + + /** + * The container of local mods applied to run configs. + * + * @return the container + * @see ForgeLocalMod + */ + NamedDomainObjectContainer<ForgeLocalMod> getLocalMods(); +} diff --git a/src/main/java/net/fabricmc/loom/api/ForgeLocalMod.java b/src/main/java/net/fabricmc/loom/api/ForgeLocalMod.java new file mode 100644 index 00000000..b722eb9e --- /dev/null +++ b/src/main/java/net/fabricmc/loom/api/ForgeLocalMod.java @@ -0,0 +1,88 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.api; + +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import org.gradle.api.Named; +import org.gradle.api.Project; +import org.gradle.api.plugins.JavaPluginConvention; +import org.gradle.api.tasks.SourceSet; + +/** + * Data for a mod built from project files in a dev environment. + * This data is only used for run config generation (FML needs the paths to mod files). + */ +public class ForgeLocalMod implements Named { + private final Project project; + private final String name; + private final List<Supplier<SourceSet>> sourceSets; + + /** + * Constructs a local mod. + * + * @param project the project using this mod + * @param name the unique name of this local mod (does not have to correspond to a mod ID) + * @param sourceSets the list of source set suppliers corresponding to this mod; must be mutable + */ + public ForgeLocalMod(Project project, String name, List<Supplier<SourceSet>> sourceSets) { + this.project = project; + this.name = name; + this.sourceSets = sourceSets; + } + + /** + * Adds source sets to this local mod. + * + * <p>The source sets are resolved like this: + * <ul> + * <li>a {@link SourceSet} is used as is</li> + * <li>all other objects will be converted to source set names with {@link String#valueOf(Object)} and + * fetched with {@code sourceSets.findByName(name)}</li> + * </ul> + * + * @param sourceSets the source sets + */ + public void add(Object... sourceSets) { + for (Object sourceSet : sourceSets) { + if (sourceSet instanceof SourceSet) { + this.sourceSets.add(() -> (SourceSet) sourceSet); + } else { + this.sourceSets.add(() -> project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets().findByName(String.valueOf(sourceSet))); + } + } + } + + @Override + public String getName() { + return name; + } + + public Stream<SourceSet> getSourceSets() { + return sourceSets.stream().map(Supplier::get); + } +} diff --git a/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java b/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java index 2fd46276..1e939f3c 100644 --- a/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java +++ b/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java @@ -26,6 +26,7 @@ package net.fabricmc.loom.api; import java.io.File; import java.util.List; +import java.util.function.Consumer; import org.gradle.api.Action; import org.gradle.api.NamedDomainObjectContainer; @@ -34,14 +35,19 @@ import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; +import org.gradle.api.provider.Provider; import org.gradle.api.publish.maven.MavenPublication; import org.jetbrains.annotations.ApiStatus; import net.fabricmc.loom.api.decompilers.LoomDecompiler; +import net.fabricmc.loom.api.decompilers.architectury.ArchitecturyLoomDecompiler; import net.fabricmc.loom.api.mappings.layered.spec.LayeredMappingSpecBuilder; +import net.fabricmc.loom.configuration.ide.RunConfig; import net.fabricmc.loom.configuration.ide.RunConfigSettings; +import net.fabricmc.loom.configuration.launch.LaunchProviderSettings; import net.fabricmc.loom.configuration.processors.JarProcessor; import net.fabricmc.loom.util.DeprecationHelper; +import net.fabricmc.loom.util.ModPlatform; /** * This is the public api available exposed to build scripts. @@ -218,4 +224,50 @@ public interface LoomGradleExtensionAPI { * @return the intermediary url template */ Property<String> getIntermediaryUrl(); + + // =================== + // Architectury Loom + // =================== + ListProperty<ArchitecturyLoomDecompiler> getArchGameDecompilers(); + + default void addArchDecompiler(ArchitecturyLoomDecompiler decompiler) { + getArchGameDecompilers().add(decompiler); + } + + void silentMojangMappingsLicense(); + + boolean isSilentMojangMappingsLicenseEnabled(); + + Provider<ModPlatform> getPlatform(); + + default boolean isForge() { + return getPlatform().get() == ModPlatform.FORGE; + } + + void setGenerateSrgTiny(Boolean generateSrgTiny); + + boolean shouldGenerateSrgTiny(); + + void launches(Action<NamedDomainObjectContainer<LaunchProviderSettings>> action); + + NamedDomainObjectContainer<LaunchProviderSettings> getLaunchConfigs(); + + default void addTaskBeforeRun(String task) { + this.getTasksBeforeRun().add(task); + } + + List<String> getTasksBeforeRun(); + + List<Consumer<RunConfig>> getSettingsPostEdit(); + + /** + * Gets the Forge extension used to configure Forge details. + * + * @return the Forge extension + * @throws UnsupportedOperationException if running on another platform + * @see #isForge() + */ + ForgeExtensionAPI getForge(); + + void forge(Action<ForgeExtensionAPI> action); } diff --git a/src/main/java/net/fabricmc/loom/api/decompilers/architectury/ArchitecturyLoomDecompiler.java b/src/main/java/net/fabricmc/loom/api/decompilers/architectury/ArchitecturyLoomDecompiler.java new file mode 100644 index 00000000..c6988b8f --- /dev/null +++ b/src/main/java/net/fabricmc/loom/api/decompilers/architectury/ArchitecturyLoomDecompiler.java @@ -0,0 +1,55 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.api.decompilers.architectury; + +import org.gradle.api.Project; + +import net.fabricmc.loom.api.decompilers.LoomDecompiler; + +/** + * A decompiler definition. Differs from {@link LoomDecompiler} by allowing + * to use a project context for creating the decompiler, which lets you make + * configurable decompilers. + * + * <p>Note that JVM forking is not handled by this interface, and that is + * the responsibility of the decompiler implementation. + */ +public interface ArchitecturyLoomDecompiler { + /** + * {@return the name of the decompiler} + * It is used for naming the source generation task ({@code genSourcesWith[name]}). + */ + String name(); + + /** + * Creates a {@link LoomDecompiler} from a {@link Project} context. + * + * <p>The name of the created decompiler is not used. + * + * @param project the project context + * @return the created decompiler + */ + LoomDecompiler create(Project project); +} diff --git a/src/main/java/net/fabricmc/loom/api/mappings/layered/MappingsNamespace.java b/src/main/java/net/fabricmc/loom/api/mappings/layered/MappingsNamespace.java index ae32f7f9..eb6ae9bc 100644 --- a/src/main/java/net/fabricmc/loom/api/mappings/layered/MappingsNamespace.java +++ b/src/main/java/net/fabricmc/loom/api/mappings/layered/MappingsNamespace.java @@ -43,6 +43,7 @@ public enum MappingsNamespace { * @see <a href="https://github.com/FabricMC/intermediary/">github.com/FabricMC/intermediary/</a> */ INTERMEDIARY, + SRG, /** * Named mappings are the developer friendly names used to develop mods against. diff --git a/src/main/java/net/fabricmc/loom/api/mappings/layered/spec/LayeredMappingSpecBuilder.java b/src/main/java/net/fabricmc/loom/api/mappings/layered/spec/LayeredMappingSpecBuilder.java index 1ed527e0..1d3de709 100644 --- a/src/main/java/net/fabricmc/loom/api/mappings/layered/spec/LayeredMappingSpecBuilder.java +++ b/src/main/java/net/fabricmc/loom/api/mappings/layered/spec/LayeredMappingSpecBuilder.java @@ -71,6 +71,8 @@ public interface LayeredMappingSpecBuilder { LayeredMappingSpecBuilder parchment(Object object, Action<ParchmentMappingsSpecBuilder> action); + LayeredMappingSpecBuilder crane(Object object); + /** * Add a signatureFix layer. Reads the @extras/record_signatures.json" file in a jar file such as yarn. */ diff --git a/src/main/java/net/fabricmc/loom/build/JarRemapper.java b/src/main/java/net/fabricmc/loom/build/JarRemapper.java index 47158bc0..5996401b 100644 --- a/src/main/java/net/fabricmc/loom/build/JarRemapper.java +++ b/src/main/java/net/fabricmc/loom/build/JarRemapper.java @@ -35,15 +35,17 @@ import java.util.Set; import java.util.function.BiConsumer; import java.util.function.BiFunction; +import dev.architectury.tinyremapper.IMappingProvider; +import dev.architectury.tinyremapper.InputTag; +import dev.architectury.tinyremapper.OutputConsumerPath; +import dev.architectury.tinyremapper.TinyRemapper; import org.gradle.api.Action; +import org.gradle.api.Project; import org.objectweb.asm.commons.Remapper; import net.fabricmc.loom.util.CloseableList; +import net.fabricmc.loom.util.LoggerFilter; import net.fabricmc.stitch.util.Pair; -import net.fabricmc.tinyremapper.IMappingProvider; -import net.fabricmc.tinyremapper.InputTag; -import net.fabricmc.tinyremapper.OutputConsumerPath; -import net.fabricmc.tinyremapper.TinyRemapper; public class JarRemapper { private final List<IMappingProvider> mappingProviders = new ArrayList<>(); @@ -65,8 +67,11 @@ public class JarRemapper { return data; } - public void remap() throws IOException { + public void remap(Project project) throws IOException { + LoggerFilter.replaceSystemOut(); TinyRemapper.Builder remapperBuilder = TinyRemapper.newRemapper(); + remapperBuilder.logger(project.getLogger()::lifecycle); + remapperBuilder.logUnknownInvokeDynamic(false); mappingProviders.forEach(remapperBuilder::withMappings); if (remapOptions != null) { @@ -79,7 +84,7 @@ public class JarRemapper { Path[] remapClasspath = classPath.stream() .filter(path -> - remapData.stream().noneMatch(remapData -> remapData.input.equals(path)) + remapData.stream().noneMatch(remapData -> remapData.input.toString().equals(path.toString())) ) .toArray(Path[]::new); @@ -88,13 +93,28 @@ public class JarRemapper { for (RemapData data : remapData) { InputTag tag = remapper.createInputTag(); data.tag = tag; - remapper.readInputsAsync(tag, data.input); + project.getLogger().info(":remapper input -> " + data.input.getFileName().toString()); + + try { + remapper.readInputsAsync(tag, data.input); + } catch (Exception e) { + throw new RuntimeException("Failed to read remapper input " + data.input.getFileName().toString(), e); + } } //noinspection MismatchedQueryAndUpdateOfCollection try (CloseableList<OutputConsumerPath> outputConsumers = new CloseableList<>()) { for (RemapData data : remapData) { - OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(data.output).build(); + OutputConsumerPath outputConsumer; + project.getLogger().info(":remapper output -> " + data.output.getFileName().toString()); + + try { + Files.deleteIfExists(data.output); + outputConsumer = new OutputConsumerPath.Builder(data.output).build(); + } catch (Exception e) { + throw new RuntimeException("Failed to create remapper output " + data.output.getFileName().toString(), e); + } + outputConsumers.add(outputConsumer); outputConsumer.addNonClassFiles(data.input); diff --git a/src/main/java/net/fabricmc/loom/build/MixinRefmapHelper.java b/src/main/java/net/fabricmc/loom/build/MixinRefmapHelper.java index 4d1ba261..580d5659 100644 --- a/src/main/java/net/fabricmc/loom/build/MixinRefmapHelper.java +++ b/src/main/java/net/fabricmc/loom/build/MixinRefmapHelper.java @@ -60,7 +60,7 @@ public final class MixinRefmapHelper { MixinExtension mixin = LoomGradleExtension.get(project).getMixin(); File output = outputPath.toFile(); - Collection<String> allMixinConfigs = getMixinConfigurationFiles(readFabricModJson(output)); + Collection<String> allMixinConfigs = LoomGradleExtension.get(project).isForge() ? LoomGradleExtension.get(project).getForge().getMixinConfigs().get() : getMixinConfigurationFiles(readFabricModJson(output)); return mixin.getMixinSourceSetsStream().map(sourceSet -> { MixinExtension.MixinInformationContainer container = Objects.requireNonNull( diff --git a/src/main/java/net/fabricmc/loom/build/ModCompileRemapper.java b/src/main/java/net/fabricmc/loom/build/ModCompileRemapper.java index b6150fb0..76475caa 100644 --- a/src/main/java/net/fabricmc/loom/build/ModCompileRemapper.java +++ b/src/main/java/net/fabricmc/loom/build/ModCompileRemapper.java @@ -31,6 +31,7 @@ import java.util.List; import java.util.function.Supplier; import java.util.zip.ZipFile; +import com.google.common.base.Suppliers; import com.google.common.io.Files; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; @@ -88,10 +89,12 @@ public class ModCompileRemapper { for (ResolvedArtifact artifact : sourceConfig.getResolvedConfiguration().getResolvedArtifacts()) { String group = replaceIfNullOrEmpty(artifact.getModuleVersion().getId().getGroup(), () -> MISSING_GROUP); - String name = artifact.getModuleVersion().getId().getName(); - String version = replaceIfNullOrEmpty(artifact.getModuleVersion().getId().getVersion(), () -> Checksum.truncatedSha256(artifact.getFile())); + // Awful fix for https://github.com/architectury/architectury-loom/issues/42 for now + Supplier<String> checksum = Suppliers.memoize(() -> Checksum.truncatedSha256(artifact.getFile())); + String name = extension.isForgeAndOfficial() ? "B" + checksum.get() : artifact.getModuleVersion().getId().getName(); + String version = extension.isForgeAndOfficial() ? "B" + checksum.get() : replaceIfNullOrEmpty(artifact.getModuleVersion().getId().getVersion(), () -> Checksum.truncatedSha256(artifact.getFile())); - if (!isFabricMod(logger, artifact.getFile(), artifact.getId())) { + if (!shouldRemapMod(logger, artifact.getFile(), artifact.getId(), extension.isForge(), sourceConfig.getName())) { addToRegularCompile(project, regularConfig, artifact); continue; } @@ -118,7 +121,7 @@ public class ModCompileRemapper { // Create a mod dependency for each file in the file collection for (File artifact : files) { - if (!isFabricMod(logger, artifact, artifact.getName())) { + if (!shouldRemapMod(logger, artifact, artifact.getName(), extension.isForge(), sourceConfig.getName())) { dependencies.add(regularConfig.getName(), project.files(artifact)); continue; } @@ -150,6 +153,7 @@ public class ModCompileRemapper { // Add all of the remapped mods onto the config for (ModDependencyInfo info : modDependencies) { + project.getLogger().info(":adding " + info.toString() + " into " + info.targetConfig.getName()); project.getDependencies().add(info.targetConfig.getName(), info.getRemappedNotation()); } @@ -169,13 +173,28 @@ public class ModCompileRemapper { /** * Checks if an artifact is a fabric mod, according to the presence of a fabric.mod.json. */ - private static boolean isFabricMod(Logger logger, File artifact, Object id) { + private static boolean shouldRemapMod(Logger logger, File artifact, Object id, boolean forge, String config) { try (ZipFile zipFile = new ZipFile(artifact)) { - if (zipFile.getEntry("fabric.mod.json") != null) { - logger.info("Found Fabric mod in modCompile: {}", id); + if (zipFile.getEntry("architectury.common.marker") != null) { + logger.info("Found architectury common mod in " + config + ": {}", id); return true; } + if (forge) { + if (zipFile.getEntry("META-INF/mods.toml") != null) { + logger.info("Found Forge mod in " + config + ": {}", id); + return true; + } + + logger.lifecycle(":could not find forge mod in " + config + " but forcing: {}", id); + return true; + } else { + if (zipFile.getEntry("fabric.mod.json") != null) { + logger.info("Found Fabric mod in " + config + ": {}", id); + return true; + } + } + return false; } catch (IOException e) { return false; diff --git a/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java b/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java index f87ccc21..33fec4e6 100644 --- a/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java +++ b/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java @@ -26,6 +26,7 @@ package net.fabricmc.loom.build.mixin; import java.io.File; import java.io.IOException; +import java.nio.file.Path; import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -81,8 +82,9 @@ public abstract class AnnotationProcessorInvoker<T extends Task> { try { LoomGradleExtension loom = LoomGradleExtension.get(project); String refmapName = Objects.requireNonNull(MixinExtension.getMixinInformationContainer(sourceSet)).refmapNameProvider().get(); + Path mappings = loom.isForge() ? loom.getMappingsProvider().mixinTinyMappingsWithSrg : loom.getMappingsProvider().tinyMappings; Map<String, String> args = new HashMap<>() {{ - put(Constants.MixinArguments.IN_MAP_FILE_NAMED_INTERMEDIARY, loom.getMappingsProvider().tinyMappings.toFile().getCanonicalPath()); + put(Constants.MixinArguments.IN_MAP_FILE_NAMED_INTERMEDIARY, mappings.toFile().getCanonicalPath()); put(Constants.MixinArguments.OUT_MAP_FILE_NAMED_INTERMEDIARY, loom.getNextMixinMappings().getCanonicalPath()); put(Constants.MixinArguments.OUT_REFMAP_FILE, getRefmapDestination(task, refmapName)); put(Constants.MixinArguments.DEFAULT_OBFUSCATION_ENV, "named:intermediary"); @@ -109,6 +111,10 @@ public abstract class AnnotationProcessorInvoker<T extends Task> { configs.getByName(Constants.Configurations.MAPPINGS_FINAL) ); + if (extension.isForge()) { + processorConfig.extendsFrom(configs.getByName(Constants.Configurations.FORGE_NAMED)); + } + // Add Mixin and mixin extensions (fabric-mixin-compile-extensions pulls mixin itself too) project.getDependencies().add(processorConfig.getName(), Constants.Dependencies.MIXIN_COMPILE_EXTENSIONS + Constants.Dependencies.Versions.MIXIN_COMPILE_EXTENSIONS); diff --git a/src/main/java/net/fabricmc/loom/build/nesting/EmptyNestedJarProvider.java b/src/main/java/net/fabricmc/loom/build/nesting/EmptyNestedJarProvider.java new file mode 100644 index 00000000..8a305d49 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/build/nesting/EmptyNestedJarProvider.java @@ -0,0 +1,38 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.build.nesting; + +import java.io.File; +import java.util.Collection; +import java.util.Collections; + +public enum EmptyNestedJarProvider implements NestedJarProvider { + INSTANCE; + + @Override + public Collection<File> provide() { + return Collections.emptyList(); + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java index f99d7216..14c4c720 100644 --- a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java @@ -42,8 +42,16 @@ import net.fabricmc.loom.build.mixin.ScalaApInvoker; import net.fabricmc.loom.configuration.ide.SetupIntelijRunConfigs; import net.fabricmc.loom.configuration.providers.LaunchProvider; import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl; +import net.fabricmc.loom.configuration.providers.forge.FieldMigratedMappingsProvider; +import net.fabricmc.loom.configuration.providers.forge.ForgeProvider; +import net.fabricmc.loom.configuration.providers.forge.ForgeUniversalProvider; +import net.fabricmc.loom.configuration.providers.forge.ForgeUserdevProvider; +import net.fabricmc.loom.configuration.providers.forge.McpConfigProvider; +import net.fabricmc.loom.configuration.providers.forge.PatchProvider; +import net.fabricmc.loom.configuration.providers.forge.SrgProvider; import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; import net.fabricmc.loom.extension.MixinExtension; +import net.fabricmc.loom.task.GenVsCodeProjectTask; import net.fabricmc.loom.util.Constants; public final class CompileConfiguration { @@ -53,13 +61,59 @@ public final class CompileConfiguration { public static void setupConfigurations(Project project) { LoomGradleExtension extension = LoomGradleExtension.get(project); + project.afterEvaluate(project1 -> { + if (extension.shouldGenerateSrgTiny()) { + extension.createLazyConfiguration(Constants.Configurations.SRG).configure(configuration -> configuration.setTransitive(false)); + } + + if (extension.isDataGenEnabled()) { + project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets().getByName("main").resources(files -> { + files.srcDir(project.file("src/generated/resources")); + }); + } + }); + extension.createLazyConfiguration(Constants.Configurations.MOD_COMPILE_CLASSPATH).configure(configuration -> configuration.setTransitive(true)); extension.createLazyConfiguration(Constants.Configurations.MOD_COMPILE_CLASSPATH_MAPPED).configure(configuration -> configuration.setTransitive(false)); extension.createLazyConfiguration(Constants.Configurations.MINECRAFT_NAMED).configure(configuration -> configuration.setTransitive(false)); // The launchers do not recurse dependencies extension.createLazyConfiguration(Constants.Configurations.MINECRAFT_DEPENDENCIES).configure(configuration -> configuration.setTransitive(false)); extension.createLazyConfiguration(Constants.Configurations.LOADER_DEPENDENCIES).configure(configuration -> configuration.setTransitive(false)); extension.createLazyConfiguration(Constants.Configurations.MINECRAFT).configure(configuration -> configuration.setTransitive(false)); - extension.createLazyConfiguration(Constants.Configurations.INCLUDE).configure(configuration -> configuration.setTransitive(false)); // Dont get transitive deps + + if (extension.isForge()) { + extension.createLazyConfiguration(Constants.Configurations.FORGE).configure(configuration -> configuration.setTransitive(false)); + extension.createLazyConfiguration(Constants.Configurations.FORGE_USERDEV).configure(configuration -> configuration.setTransitive(false)); + extension.createLazyConfiguration(Constants.Configurations.FORGE_INSTALLER).configure(configuration -> configuration.setTransitive(false)); + extension.createLazyConfiguration(Constants.Configurations.FORGE_UNIVERSAL).configure(configuration -> configuration.setTransitive(false)); + extension.createLazyConfiguration(Constants.Configurations.FORGE_DEPENDENCIES); + extension.createLazyConfiguration(Constants.Configurations.FORGE_NAMED).configure(configuration -> configuration.setTransitive(false)); + extension.createLazyConfiguration(Constants.Configurations.FORGE_EXTRA).configure(configuration -> configuration.setTransitive(false)); + extension.createLazyConfiguration(Constants.Configurations.MCP_CONFIG).configure(configuration -> configuration.setTransitive(false)); + extension.createLazyConfiguration(Constants.Configurations.FORGE_RUNTIME_LIBRARY); + + extendsFrom(Constants.Configurations.MINECRAFT_DEPENDENCIES, Constants.Configurations.FORGE_DEPENDENCIES, project); + + extendsFrom(Constants.Configurations.FORGE_RUNTIME_LIBRARY, Constants.Configurations.FORGE_DEPENDENCIES, project); + extendsFrom(Constants.Configurations.FORGE_RUNTIME_LIBRARY, Constants.Configurations.MINECRAFT_DEPENDENCIES, project); + extendsFrom(Constants.Configurations.FORGE_RUNTIME_LIBRARY, Constants.Configurations.FORGE_EXTRA, project); + extendsFrom(Constants.Configurations.FORGE_RUNTIME_LIBRARY, Constants.Configurations.MINECRAFT_NAMED, project); + extendsFrom(Constants.Configurations.FORGE_RUNTIME_LIBRARY, Constants.Configurations.FORGE_NAMED, project); + // Include any user-defined libraries on the runtime CP. + // (All the other superconfigurations are already on there.) + extendsFrom(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.FORGE_RUNTIME_LIBRARY, project); + + extendsFrom(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.FORGE_NAMED, project); + extendsFrom(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.FORGE_NAMED, project); + extendsFrom(JavaPlugin.TEST_COMPILE_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.FORGE_NAMED, project); + extendsFrom(JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.FORGE_NAMED, project); + extendsFrom(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.FORGE_EXTRA, project); + extendsFrom(JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.FORGE_EXTRA, project); + } + + if (extension.supportsInclude()) { + extension.createLazyConfiguration(Constants.Configurations.INCLUDE).configure(configuration -> configuration.setTransitive(false)); // Dont get transitive deps + } + extension.createLazyConfiguration(Constants.Configurations.MAPPING_CONSTANTS); extension.createLazyConfiguration(Constants.Configurations.NAMED_ELEMENTS).configure(configuration -> { configuration.setCanBeConsumed(true); @@ -131,7 +185,23 @@ public final class CompileConfiguration { extension.setDependencyManager(dependencyManager); dependencyManager.addProvider(new MinecraftProviderImpl(project)); - dependencyManager.addProvider(new MappingsProviderImpl(project)); + + if (extension.isForge()) { + dependencyManager.addProvider(new ForgeProvider(project)); + dependencyManager.addProvider(new ForgeUserdevProvider(project)); + } + + if (extension.shouldGenerateSrgTiny()) { + dependencyManager.addProvider(new SrgProvider(project)); + } + + if (extension.isForge()) { + dependencyManager.addProvider(new McpConfigProvider(project)); + dependencyManager.addProvider(new PatchProvider(project)); + dependencyManager.addProvider(new ForgeUniversalProvider(project)); + } + + dependencyManager.addProvider(extension.isForge() ? new FieldMigratedMappingsProvider(project) : new MappingsProviderImpl(project)); dependencyManager.addProvider(new LaunchProvider(project)); dependencyManager.handleDependencies(project); @@ -141,6 +211,7 @@ public final class CompileConfiguration { project.getTasks().getByName("cleanEclipse").finalizedBy(project.getTasks().getByName("cleanEclipseRuns")); SetupIntelijRunConfigs.setup(project); + GenVsCodeProjectTask.generate(project); extension.getRemapArchives().finalizeValue(); // Enables the default mod remapper diff --git a/src/main/java/net/fabricmc/loom/configuration/DependencyProvider.java b/src/main/java/net/fabricmc/loom/configuration/DependencyProvider.java index 74642f79..c50102d7 100644 --- a/src/main/java/net/fabricmc/loom/configuration/DependencyProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/DependencyProvider.java @@ -202,7 +202,7 @@ public abstract class DependencyProvider { this.resolvedFiles = files; switch (files.size()) { case 0 -> //Don't think Gradle would ever let you do this - throw new IllegalStateException("Empty dependency?"); + throw new IllegalStateException("Empty dependency for " + configuration.getName()); case 1 -> //Single file dependency classifierToFile.put("", Iterables.getOnlyElement(files)); default -> { //File collection, try work out the classifiers diff --git a/src/main/java/net/fabricmc/loom/configuration/JarManifestConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/JarManifestConfiguration.java index eeeb4d20..edfa2ca6 100644 --- a/src/main/java/net/fabricmc/loom/configuration/JarManifestConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/JarManifestConfiguration.java @@ -28,6 +28,7 @@ import java.util.Optional; import java.util.jar.Attributes; import java.util.jar.Manifest; +import dev.architectury.tinyremapper.TinyRemapper; import org.gradle.api.Project; import org.gradle.api.artifacts.Dependency; import org.gradle.util.GradleVersion; @@ -35,7 +36,6 @@ import org.gradle.util.GradleVersion; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.LoomGradlePlugin; import net.fabricmc.loom.util.Constants; -import net.fabricmc.tinyremapper.TinyRemapper; public final record JarManifestConfiguration(Project project) { public void configure(Manifest manifest) { diff --git a/src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java b/src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java index 29ae1d03..af080877 100644 --- a/src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java +++ b/src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java @@ -77,7 +77,7 @@ public class LoomDependencyManager { return provider; } - public <T> T getProvider(Class<T> clazz) { + public <T> T getProvider(Class<? extends T> clazz) { for (DependencyProvider provider : dependencyProviderList) { if (provider.getClass() == clazz) { return (T) provider; @@ -135,16 +135,17 @@ public class LoomDependencyManager { try { provider.provide(info, afterTasks::add); } catch (Exception e) { - throw new RuntimeException("Failed to provide " + dependency.getGroup() + ":" + dependency.getName() + ":" + dependency.getVersion() + " : " + e.toString(), e); + throw new RuntimeException("Failed to provide " + dependency.getGroup() + ":" + dependency.getName() + ":" + dependency.getVersion() + " : " + e.toString() + "\n\tEnsure minecraft is not open and try running with --refresh-dependencies. Use --stacktrace to see the full stacktrace.", e); } } } } SourceRemapper sourceRemapper = new SourceRemapper(project, true); - String mappingsIdentifier = mappingsProvider.mappingsIdentifier(); + String platformSuffix = extension.isForge() ? "_forge" : ""; + String mappingsIdentifier = mappingsProvider.mappingsIdentifier() + platformSuffix; - if (extension.getInstallerData() == null) { + if (extension.getInstallerData() == null && !extension.isForge()) { //If we've not found the installer JSON we've probably skipped remapping Fabric loader, let's go looking project.getLogger().info("Searching through modCompileClasspath for installer JSON"); final Configuration configuration = project.getConfigurations().getByName(Constants.Configurations.MOD_COMPILE_CLASSPATH); @@ -165,10 +166,10 @@ public class LoomDependencyManager { } } } - } - if (extension.getInstallerData() == null) { - project.getLogger().warn("fabric-installer.json not found in classpath!"); + if (extension.getInstallerData() == null) { + project.getLogger().warn("fabric-installer.json not found in classpath!"); + } } ModCompileRemapper.remapDependencies(project, mappingsIdentifier, extension, sourceRemapper); diff --git a/src/main/java/net/fabricmc/loom/configuration/MavenPublication.java b/src/main/java/net/fabricmc/loom/configuration/MavenPublication.java index 29c312ea..ef84fda0 100644 --- a/src/main/java/net/fabricmc/loom/configuration/MavenPublication.java +++ b/src/main/java/net/fabricmc/loom/configuration/MavenPublication.java @@ -71,6 +71,7 @@ public final class MavenPublication { PublishingExtension mavenPublish = p.getExtensions().findByType(PublishingExtension.class); if (mavenPublish != null) { + p.getLogger().info("Processing maven publication for project [" + p.getName() + "] of " + configurationName); processEntry(project, scope, config, mavenPublish, reportedDeprecation); } }); @@ -97,12 +98,13 @@ public final class MavenPublication { if (hasSoftwareComponent(publication) || EXCLUDED_PUBLICATIONS.contains(publication)) { continue; - } else if (!reportedDeprecation.get()) { + } else if (!reportedDeprecation.get() && !LoomGradleExtension.get(project).isForge()) { DeprecationHelper deprecationHelper = LoomGradleExtension.get(project).getDeprecationHelper(); deprecationHelper.warn("Loom is applying dependency data manually to publications instead of using a software component (from(components[\"java\"])). This is deprecated and will be removed in Loom 0.12."); reportedDeprecation.set(true); } + project.getLogger().info("Processing maven publication [" + publication.getName() + "]"); mavenPublication.pom((pom) -> pom.withXml((xml) -> { Node dependencies = GroovyXmlUtil.getOrCreateNode(xml.asNode(), "dependencies"); Set<String> foundArtifacts = new HashSet<>(); @@ -118,9 +120,12 @@ public final class MavenPublication { for (Dependency dependency : config.getAllDependencies()) { if (foundArtifacts.contains(dependency.getGroup() + ":" + dependency.getName())) { + project.getLogger().info("Found inserted artifact " + dependency.getGroup() + ":" + dependency.getName()); continue; } + project.getLogger().info("Inserting artifact " + dependency.getGroup() + ":" + dependency.getName()); + Node depNode = dependencies.appendNode("dependency"); depNode.appendNode("groupId", dependency.getGroup()); depNode.appendNode("artifactId", dependency.getName()); diff --git a/src/main/java/net/fabricmc/loom/configuration/RemapConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/RemapConfiguration.java index efbf5a1d..baf33086 100644 --- a/src/main/java/net/fabricmc/loom/configuration/RemapConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/RemapConfiguration.java @@ -25,8 +25,10 @@ package net.fabricmc.loom.configuration; import java.io.IOException; +import java.util.Set; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; import org.gradle.api.Action; import org.gradle.api.Project; import org.gradle.api.Task; @@ -37,6 +39,7 @@ import org.gradle.api.artifacts.PublishArtifact; import org.gradle.api.artifacts.dsl.ArtifactHandler; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.tasks.bundling.AbstractArchiveTask; +import org.gradle.api.tasks.bundling.Jar; import org.jetbrains.annotations.ApiStatus; import net.fabricmc.loom.LoomGradleExtension; @@ -46,7 +49,9 @@ import net.fabricmc.loom.task.AbstractLoomTask; import net.fabricmc.loom.task.RemapAllSourcesTask; import net.fabricmc.loom.task.RemapJarTask; import net.fabricmc.loom.task.RemapSourcesJarTask; +import net.fabricmc.loom.util.PropertyUtil; import net.fabricmc.loom.util.SourceRemapper; +import net.fabricmc.loom.util.aw2at.Aw2At; public class RemapConfiguration { private static final String DEFAULT_JAR_TASK_NAME = JavaPlugin.JAR_TASK_NAME; @@ -81,6 +86,9 @@ public class RemapConfiguration { // It doesn't seem to hurt, but I'm not sure if the file-level duplicates cause issues. Configuration configuration = project.getConfigurations().getByName(JavaPlugin.SOURCES_ELEMENTS_CONFIGURATION_NAME); configuration.getArtifacts().removeIf(a -> a != artifact && artifact.getFile().equals(a.getFile())); + // Architectury Loom Patch + // Since we make sourcesJar and remapSourcesJar output files separate, we have to remove the dev sources jar file here + configuration.getArtifacts().removeIf(a -> a != artifact && task.getInput().get().getAsFile().equals(a.getFile())); }); // Remove -dev jars from the default jar task @@ -114,12 +122,31 @@ public class RemapConfiguration { remapJarTask.getInput().convention(jarTask.getArchiveFile()); } + if (extension.isForge()) { + Set<String> mixinConfigs = PropertyUtil.getAndFinalize(extension.getForge().getMixinConfigs()); + + if (!mixinConfigs.isEmpty()) { + ((Jar) jarTask).manifest(manifest -> { + manifest.attributes(ImmutableMap.of("MixinConfigs", String.join(",", mixinConfigs))); + }); + } + } + if (isDefaultRemap) { extension.getUnmappedModCollection().from(jarTask); remapJarTask.getAddNestedDependencies().convention(true); remapJarTask.getRemapAccessWidener().convention(true); project.getArtifacts().add("archives", remapJarTask); + + if (extension.isForge()) { + boolean convertAws = PropertyUtil.getAndFinalize(extension.getForge().getConvertAccessWideners()); + + if (convertAws) { + Aw2At.setup(project, remapJarTask); + remapJarTask.getRemapAccessWidener().set(false); + } + } } remapJarTask.dependsOn(jarTask); @@ -127,7 +154,7 @@ public class RemapConfiguration { // TODO this might be wrong? project.getTasks().withType(RemapJarTask.class).forEach(task -> { - if (task.getAddNestedDependencies().getOrElse(false)) { + if (extension.supportsInclude() && task.getAddNestedDependencies().getOrElse(false)) { NestedDependencyProvider.getRequiredTasks(project).forEach(task::dependsOn); } }); @@ -155,7 +182,7 @@ public class RemapConfiguration { rootProject.getTasks().register(remapAllJarsTaskName, AbstractLoomTask.class, task -> { task.doLast(t -> { try { - jarRemapper.remap(); + jarRemapper.remap(project); } catch (IOException e) { throw new RuntimeException("Failed to remap jars", e); } @@ -179,9 +206,11 @@ public class RemapConfiguration { RemapSourcesJarTask remapSourcesJarTask = (RemapSourcesJarTask) project.getTasks().findByName(remapSourcesJarTaskName); Preconditions.checkNotNull(remapSourcesJarTask, "Could not find " + remapSourcesJarTaskName + " in " + project.getName()); + remapSourcesJarTask.getOutput().convention(sourcesTask.getArchiveFile().get()); + String sourcesTaskClassifer = sourcesTask.getArchiveClassifier().get(); + sourcesTask.getArchiveClassifier().set(sourcesTaskClassifer == null ? "dev" : sourcesTaskClassifer + "-dev"); remapSourcesJarTask.getInput().convention(sourcesTask.getArchiveFile()); - remapSourcesJarTask.getOutput().convention(sourcesTask.getArchiveFile()); - remapSourcesJarTask.dependsOn(project.getTasks().getByName(sourcesJarTaskName)); + remapSourcesJarTask.dependsOn(sourcesTask); if (isDefaultRemap) { // Do not use lambda here, see: https://github.com/gradle/gradle/pull/17087 diff --git a/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerFile.java b/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerFile.java index 893a1611..4961e71d 100644 --- a/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerFile.java +++ b/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerFile.java @@ -54,6 +54,44 @@ public record AccessWidenerFile( } if (modJsonBytes == null) { + if (ZipUtils.contains(modJarPath, "architectury.common.json")) { + String awPath = null; + byte[] commonJsonBytes; + + try { + commonJsonBytes = ZipUtils.unpackNullable(modJarPath, "architectury.common.json"); + } catch (IOException e) { + throw new UncheckedIOException("Failed to read access-widener file from: " + modJarPath.toAbsolutePath(), e); + } + + if (commonJsonBytes != null) { + JsonObject jsonObject = new Gson().fromJson(new String(commonJsonBytes, StandardCharsets.UTF_8), JsonObject.class); + + if (jsonObject.has("accessWidener")) { + awPath = jsonObject.get("accessWidener").getAsString(); + } else { + throw new IllegalArgumentException("The architectury.common.json file does not contain an accessWidener field."); + } + } else { + // ??????????? + throw new IllegalArgumentException("The architectury.common.json file does not exist."); + } + + byte[] content; + + try { + content = ZipUtils.unpack(modJarPath, awPath); + } catch (IOException e) { + throw new UncheckedIOException("Could not find access widener file (%s) defined in the architectury.common.json file of %s".formatted(awPath, modJarPath.toAbsolutePath()), e); + } + + return new AccessWidenerFile( + awPath, + modJarPath.getFileName().toString(), + content + ); + } + return null; } diff --git a/src/main/java/net/fabricmc/loom/configuration/accesswidener/TransitiveAccessWidenerJarProcessor.java b/src/main/java/net/fabricmc/loom/configuration/accesswidener/TransitiveAccessWidenerJarProcessor.java index d5f234bd..9ab13264 100644 --- a/src/main/java/net/fabricmc/loom/configuration/accesswidener/TransitiveAccessWidenerJarProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/accesswidener/TransitiveAccessWidenerJarProcessor.java @@ -34,6 +34,7 @@ import java.util.List; import java.util.Set; import com.google.common.base.Preconditions; +import dev.architectury.tinyremapper.TinyRemapper; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.FileCollectionDependency; @@ -51,7 +52,6 @@ import net.fabricmc.loom.configuration.RemappedConfigurationEntry; import net.fabricmc.loom.configuration.processors.JarProcessor; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.TinyRemapperHelper; -import net.fabricmc.tinyremapper.TinyRemapper; /** * Applies transitive access wideners that are inherited from mod and api dependencies. diff --git a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java index 3e23dec3..0d180e6b 100644 --- a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java +++ b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java @@ -29,13 +29,16 @@ import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.function.Consumer; import com.google.common.collect.ImmutableMap; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import org.apache.tools.ant.util.StringUtils; import org.gradle.api.Project; import org.gradle.api.tasks.SourceSet; import org.gradle.plugins.ide.eclipse.model.EclipseModel; @@ -50,11 +53,14 @@ public class RunConfig { public String configName; public String eclipseProjectName; public String ideaModuleName; + public String vscodeProjectName; public String mainClass; public String runDirIdeaUrl; public String runDir; public List<String> vmArgs = new ArrayList<>(); public List<String> programArgs = new ArrayList<>(); + public List<String> vscodeBeforeRun = new ArrayList<>(); + public final Map<String, String> envVariables = new HashMap<>(); public SourceSet sourceSet; public Element genRuns(Element doc) { @@ -73,6 +79,14 @@ public class RunConfig { this.addXml(root, "option", ImmutableMap.of("name", "PROGRAM_PARAMETERS", "value", joinArguments(programArgs))); } + if (!envVariables.isEmpty()) { + Element envs = this.addXml(root, "envs", ImmutableMap.of()); + + for (Map.Entry<String, String> envEntry : envVariables.entrySet()) { + this.addXml(envs, "env", ImmutableMap.of("name", envEntry.getKey(), "value", envEntry.getValue())); + } + } + return root; } @@ -106,6 +120,7 @@ public class RunConfig { private static void populate(Project project, LoomGradleExtension extension, RunConfig runConfig, String environment) { runConfig.configName += extension.isRootProject() ? "" : " (" + project.getPath() + ")"; runConfig.eclipseProjectName = project.getExtensions().getByType(EclipseModel.class).getProject().getName(); + runConfig.vscodeProjectName = extension.isRootProject() ? "" : StringUtils.removePrefix(project.getPath(), ":"); runConfig.mainClass = "net.fabricmc.devlaunchinjector.Main"; runConfig.vmArgs.add("-Dfabric.dli.config=" + encodeEscaped(extension.getFiles().getDevLauncherConfig().getAbsolutePath())); @@ -123,6 +138,7 @@ public class RunConfig { } public static RunConfig runConfig(Project project, RunConfigSettings settings) { + settings.evaluateNow(); LoomGradleExtension extension = LoomGradleExtension.get(project); String name = settings.getName(); @@ -156,6 +172,7 @@ public class RunConfig { } RunConfig runConfig = new RunConfig(); + runConfig.envVariables.putAll(settings.envVariables); runConfig.configName = configName; populate(project, extension, runConfig, environment); runConfig.ideaModuleName = getIdeaModuleName(project, sourceSet); @@ -168,6 +185,10 @@ public class RunConfig { runConfig.vmArgs.addAll(settings.getVmArgs()); runConfig.vmArgs.add("-Dfabric.dli.main=" + getMainClass(environment, extension, defaultMain)); + for (Consumer<RunConfig> consumer : extension.getSettingsPostEdit()) { + consumer.accept(runConfig); + } + return runConfig; } @@ -196,6 +217,25 @@ public class RunConfig { dummyConfig = dummyConfig.replace("%PROGRAM_ARGS%", joinArguments(programArgs).replaceAll("\"", """)); dummyConfig = dummyConfig.replace("%VM_ARGS%", joinArguments(vmArgs).replaceAll("\"", """)); + String envs = ""; + + if (!envVariables.isEmpty()) { + StringBuilder builder = new StringBuilder("<envs>"); + + for (Map.Entry<String, String> env : envVariables.entrySet()) { + builder.append("<env name=\""); + builder.append(env.getKey().replaceAll("\"", """)); + builder.append("\" value=\""); + builder.append(env.getValue().replaceAll("\"", """)); + builder.append("\"/>"); + } + + builder.append("</envs>"); + envs = builder.toString(); + } + + dummyConfig = dummyConfig.replace("%ENVS%", envs); + return dummyConfig; } @@ -216,7 +256,7 @@ public class RunConfig { } private static String getMainClass(String side, LoomGradleExtension extension, String defaultMainClass) { - InstallerData installerData = extension.getInstallerData(); + InstallerData installerData = extension.getInstallerData() == null ? null : extension.getInstallerData(); if (installerData == null) { return defaultMainClass; diff --git a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfigSettings.java b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfigSettings.java index a7a1c428..793f5f1a 100644 --- a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfigSettings.java +++ b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfigSettings.java @@ -28,6 +28,7 @@ import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Function; @@ -36,6 +37,7 @@ import org.gradle.api.Named; import org.gradle.api.Project; import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.tasks.SourceSet; +import org.jetbrains.annotations.ApiStatus; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.util.Constants; @@ -96,6 +98,8 @@ public final class RunConfigSettings implements Named { private final Project project; private final LoomGradleExtension extension; + public final Map<String, String> envVariables = new HashMap<>(); + private List<Runnable> evaluateLater = new ArrayList<>(); public RunConfigSettings(Project project, String baseName) { this.baseName = baseName; @@ -107,6 +111,20 @@ public final class RunConfigSettings implements Named { runDir("run"); } + @ApiStatus.Internal + public void evaluateLater(Runnable runnable) { + this.evaluateLater.add(runnable); + } + + @ApiStatus.Internal + public void evaluateNow() { + for (Runnable runnable : this.evaluateLater) { + runnable.run(); + } + + this.evaluateLater.clear(); + } + public Project getProject() { return project; } @@ -262,7 +280,7 @@ public final class RunConfigSettings implements Named { public void client() { startFirstThread(); environment("client"); - defaultMainClass(Constants.Knot.KNOT_CLIENT); + defaultMainClass(getExtension().isForge() ? Constants.Forge.LAUNCH_TESTING : Constants.Knot.KNOT_CLIENT); } /** @@ -271,7 +289,15 @@ public final class RunConfigSettings implements Named { public void server() { programArg("nogui"); environment("server"); - defaultMainClass(Constants.Knot.KNOT_SERVER); + defaultMainClass(getExtension().isForge() ? Constants.Forge.LAUNCH_TESTING : Constants.Knot.KNOT_SERVER); + } + + /** + * Configure run config with the default server options. + */ + public void data() { + environment("data"); + defaultMainClass(getExtension().isForge() ? Constants.Forge.LAUNCH_TESTING : Constants.Knot.KNOT_SERVER); } /** diff --git a/src/main/java/net/fabricmc/loom/configuration/ide/SetupIntelijRunConfigs.java b/src/main/java/net/fabricmc/loom/configuration/ide/SetupIntelijRunConfigs.java index 8120a63f..34c7cff1 100644 --- a/src/main/java/net/fabricmc/loom/configuration/ide/SetupIntelijRunConfigs.java +++ b/src/main/java/net/fabricmc/loom/configuration/ide/SetupIntelijRunConfigs.java @@ -44,13 +44,13 @@ public class SetupIntelijRunConfigs { } try { - generate(project); + generate(project, false); } catch (IOException e) { throw new RuntimeException("Failed to generate run configs", e); } } - private static void generate(Project project) throws IOException { + public static void generate(Project project, boolean override) throws IOException { Project rootProject = project.getRootProject(); LoomGradleExtension extension = LoomGradleExtension.get(project); @@ -80,6 +80,10 @@ public class SetupIntelijRunConfigs { File runConfigs = new File(runConfigsDir, name + projectPath + ".xml"); String runConfigXml = config.fromDummy("idea_run_config_template.xml", true, project); + if (runConfigs.exists() && override) { + runConfigs.delete(); + } + if (!runConfigs.exists()) { FileUtils.writeStringToFile(runConfigs, runConfigXml, StandardCharsets.UTF_8); } diff --git a/src/main/java/net/fabricmc/loom/configuration/launch/LaunchProviderSettings.java b/src/main/java/net/fabricmc/loom/configuration/launch/LaunchProviderSettings.java new file mode 100644 index 00000000..0ac814b5 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/launch/LaunchProviderSettings.java @@ -0,0 +1,94 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.launch; + +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.gradle.api.Named; +import org.gradle.api.Project; +import org.jetbrains.annotations.ApiStatus; + +public class LaunchProviderSettings implements Named { + private final String name; + private List<Map.Entry<String, String>> properties = new ArrayList<>(); + private List<String> arguments = new ArrayList<>(); + private List<Runnable> evaluateLater = new ArrayList<>(); + + public LaunchProviderSettings(Project project, String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @ApiStatus.Internal + public void evaluateLater(Runnable runnable) { + this.evaluateLater.add(runnable); + } + + @ApiStatus.Internal + public void evaluateNow() { + for (Runnable runnable : this.evaluateLater) { + runnable.run(); + } + + this.evaluateLater.clear(); + } + + public void arg(String argument) { + this.arguments.add(argument); + } + + public void arg(String... arguments) { + this.arguments.addAll(Arrays.asList(arguments)); + } + + public void arg(Collection<String> arguments) { + this.arguments.addAll(arguments); + } + + public void property(String key, String value) { + this.properties.add(new AbstractMap.SimpleEntry<>(key, value)); + } + + public void properties(Map<String, String> arguments) { + this.properties.addAll(arguments.entrySet()); + } + + public List<Map.Entry<String, String>> getProperties() { + return properties; + } + + public List<String> getArguments() { + return arguments; + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java b/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java index bc888d1f..0c28cac9 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java @@ -24,6 +24,8 @@ package net.fabricmc.loom.configuration.mods; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; @@ -31,10 +33,20 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.Map; +import java.util.jar.Attributes; +import java.util.jar.Manifest; +import java.util.stream.Stream; +import com.google.common.base.Stopwatch; import com.google.gson.JsonObject; +import dev.architectury.tinyremapper.InputTag; +import dev.architectury.tinyremapper.NonClassCopyMode; +import dev.architectury.tinyremapper.OutputConsumerPath; +import dev.architectury.tinyremapper.TinyRemapper; import org.gradle.api.Project; import org.objectweb.asm.commons.Remapper; @@ -48,12 +60,13 @@ import net.fabricmc.loom.configuration.processors.dependency.ModDependencyInfo; import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider; import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.FileSystemUtil; +import net.fabricmc.loom.util.LoggerFilter; import net.fabricmc.loom.util.TinyRemapperHelper; import net.fabricmc.loom.util.ZipUtils; -import net.fabricmc.tinyremapper.InputTag; -import net.fabricmc.tinyremapper.NonClassCopyMode; -import net.fabricmc.tinyremapper.OutputConsumerPath; -import net.fabricmc.tinyremapper.TinyRemapper; +import net.fabricmc.loom.util.srg.AtRemapper; +import net.fabricmc.loom.util.srg.CoreModClassRemapper; +import net.fabricmc.mappingio.tree.MemoryMappingTree; public class ModProcessor { private static final String fromM = MappingsNamespace.INTERMEDIARY.toString(); @@ -103,6 +116,7 @@ public class ModProcessor { } private void stripNestedJars(File file) { + if (!ZipUtils.contains(file.toPath(), "fabric.mod.json")) return; // Strip out all contained jar info as we dont want loader to try and load the jars contained in dev. try { ZipUtils.transformJson(JsonObject.class, file.toPath(), Map.of("fabric.mod.json", json -> { @@ -136,19 +150,30 @@ public class ModProcessor { final LoomGradleExtension extension = LoomGradleExtension.get(project); final MinecraftMappedProvider mappedProvider = extension.getMinecraftMappedProvider(); final MappingsProviderImpl mappingsProvider = extension.getMappingsProvider(); + String fromM = extension.isForge() ? MappingsNamespace.SRG.toString() : MappingsNamespace.INTERMEDIARY.toString(); + String toM = MappingsNamespace.NAMED.toString(); - Path intermediaryJar = mappedProvider.getIntermediaryJar().toPath(); + Path intermediaryJar = extension.isForge() ? mappedProvider.getSrgJar().toPath() : mappedProvider.getIntermediaryJar().toPath(); Path[] mcDeps = project.getConfigurations().getByName(Constants.Configurations.LOADER_DEPENDENCIES).getFiles() .stream().map(File::toPath).toArray(Path[]::new); + Stopwatch stopwatch = Stopwatch.createStarted(); project.getLogger().lifecycle(":remapping " + remapList.size() + " mods (TinyRemapper, " + fromM + " -> " + toM + ")"); + MemoryMappingTree mappings = (fromM.equals("srg") || toM.equals("srg")) && extension.isForge() ? mappingsProvider.getMappingsWithSrg() : mappingsProvider.getMappings(); + LoggerFilter.replaceSystemOut(); final TinyRemapper remapper = TinyRemapper.newRemapper() - .withMappings(TinyRemapperHelper.create(mappingsProvider.getMappings(), fromM, toM, false)) + .logger(project.getLogger()::lifecycle) + .logUnknownInvokeDynamic(false) + .withMappings(TinyRemapperHelper.create(mappings, fromM, toM, false)) .renameInvalidLocals(false) .build(); remapper.readClassPathAsync(intermediaryJar); + + if (extension.isForgeAndNotOfficial()) { + remapper.readClassPathAsync(mappedProvider.getForgeSrgJar().toPath()); + } remapper.readClassPathAsync(mcDeps); final Map<ModDependencyInfo, InputTag> tagMap = new HashMap<>(); @@ -200,6 +225,8 @@ public class ModProcessor { remapper.finish(); } + project.getLogger().lifecycle(":remapped " + remapList.size() + " mods (TinyRemapper, " + fromM + " -> " + toM + ") in " + stopwatch.stop()); + for (ModDependencyInfo info : remapList) { outputConsumerMap.get(info).close(); byte[] accessWidener = accessWidenerMap.get(info); @@ -211,7 +238,63 @@ public class ModProcessor { stripNestedJars(info.getRemappedOutput()); + if (extension.isForge()) { + AtRemapper.remap(project.getLogger(), info.getRemappedOutput().toPath(), mappings); + CoreModClassRemapper.remapJar(info.getRemappedOutput().toPath(), mappings, project.getLogger()); + + ZipUtils.transform(info.getRemappedOutput().toPath(), Map.of("META-INF/MANIFEST.MF", bytes -> { + Manifest manifest = new Manifest(new ByteArrayInputStream(bytes)); + fixManifest(manifest); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + manifest.write(out); + return out.toByteArray(); + })); + + try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(info.getRemappedOutput().toPath(), false); + Stream<Path> walk = Files.walk(fs.get().getPath("/"))) { + List<Path> filesToRemove = new ArrayList<>(); + Iterator<Path> iterator = walk.iterator(); + + while (iterator.hasNext()) { + Path fsPath = iterator.next(); + if (!Files.isRegularFile(fsPath)) continue; + String fileName = fsPath.toString(); + + if (fileName.toLowerCase(Locale.ROOT).endsWith(".rsa") || fileName.toLowerCase(Locale.ROOT).endsWith(".sf")) { + if (fileName.startsWith("META-INF")) { + filesToRemove.add(fsPath); + } + } + } + + for (Path fileToRemove : filesToRemove) { + Files.delete(fileToRemove); + } + } + } + info.finaliseRemapping(); } } + + private static void fixManifest(Manifest manifest) { + Attributes mainAttrs = manifest.getMainAttributes(); + + mainAttrs.remove(Attributes.Name.SIGNATURE_VERSION); + + for (Iterator<Attributes> it = manifest.getEntries().values().iterator(); it.hasNext(); ) { + Attributes attrs = it.next(); + + for (Iterator<Object> it2 = attrs.keySet().iterator(); it2.hasNext(); ) { + Attributes.Name attrName = (Attributes.Name) it2.next(); + String name = attrName.toString(); + + if (name.endsWith("-Digest") || name.contains("-Digest-") || name.equals("Magic")) { + it2.remove(); + } + } + + if (attrs.isEmpty()) it.remove(); + } + } } diff --git a/src/main/java/net/fabricmc/loom/configuration/processors/MinecraftProcessedProvider.java b/src/main/java/net/fabricmc/loom/configuration/processors/MinecraftProcessedProvider.java index fc8aa863..744bc53e 100644 --- a/src/main/java/net/fabricmc/loom/configuration/processors/MinecraftProcessedProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/processors/MinecraftProcessedProvider.java @@ -52,7 +52,9 @@ public class MinecraftProcessedProvider extends MinecraftMappedProvider { @Override protected void addDependencies(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) { - if (jarProcessorManager.isInvalid(projectMappedJar) || isRefreshDeps()) { + boolean isForgeAtDirty = getExtension().isForge() && getExtension().getMappingsProvider().patchedProvider.isAtDirty(); + + if (jarProcessorManager.isInvalid(projectMappedJar) || isRefreshDeps() || isForgeAtDirty) { getProject().getLogger().info(":processing mapped jar"); invalidateJar(); @@ -66,7 +68,7 @@ public class MinecraftProcessedProvider extends MinecraftMappedProvider { } getProject().getDependencies().add(Constants.Configurations.MINECRAFT_NAMED, - getProject().getDependencies().module("net.minecraft:minecraft-" + projectMappedClassifier + ":" + getMinecraftProvider().minecraftVersion() + "/" + getExtension().getMappingsProvider().mappingsIdentifier())); + getProject().getDependencies().module("net.minecraft:" + minecraftProvider.getJarPrefix() + "minecraft-" + projectMappedClassifier + ":" + getMinecraftProvider().minecraftVersion() + "/" + getExtension().getMappingsProvider().mappingsIdentifier())); } private void invalidateJar() { @@ -86,7 +88,7 @@ public class MinecraftProcessedProvider extends MinecraftMappedProvider { super.initFiles(minecraftProvider, mappingsProvider); projectMappedJar = new File(getDirectories().getRootProjectPersistentCache(), getMinecraftProvider().minecraftVersion() + "/" - + getExtension().getMappingsProvider().mappingsIdentifier() + "/minecraft-" + projectMappedClassifier + ".jar"); + + getExtension().getMappingsProvider().mappingsIdentifier() + "/" + minecraftProvider.getJarPrefix() + "minecraft-" + projectMappedClassifier + ".jar"); } @Override diff --git a/src/main/java/net/fabricmc/loom/configuration/processors/dependency/ModDependencyInfo.java b/src/main/java/net/fabricmc/loom/configuration/processors/dependency/ModDependencyInfo.java index 10aa256a..5f6d0377 100644 --- a/src/main/java/net/fabricmc/loom/configuration/processors/dependency/ModDependencyInfo.java +++ b/src/main/java/net/fabricmc/loom/configuration/processors/dependency/ModDependencyInfo.java @@ -192,7 +192,18 @@ public class ModDependencyInfo { } private static AccessWidenerData tryReadAccessWidenerData(Path inputJar) throws IOException { - byte[] modJsonBytes = ZipUtils.unpack(inputJar, "fabric.mod.json"); + byte[] modJsonBytes = ZipUtils.unpackNullable(inputJar, "fabric.mod.json"); + + if (modJsonBytes == null) { + modJsonBytes = ZipUtils.unpackNullable(inputJar, "architectury.common.json"); + + if (modJsonBytes == null) { + // No access widener data + // We can just ignore in architectury + return null; + } + } + JsonObject jsonObject = LoomGradlePlugin.GSON.fromJson(new String(modJsonBytes, StandardCharsets.UTF_8), JsonObject.class); if (!jsonObject.has("accessWidener")) { diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java index f033750e..2ea5c812 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java @@ -34,6 +34,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.StringJoiner; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -45,7 +46,9 @@ import org.gradle.api.plugins.JavaPlugin; import net.fabricmc.loom.configuration.DependencyProvider; import net.fabricmc.loom.configuration.RemappedConfigurationEntry; +import net.fabricmc.loom.configuration.launch.LaunchProviderSettings; import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.PropertyUtil; public class LaunchProvider extends DependencyProvider { public LaunchProvider(Project project) { @@ -61,12 +64,65 @@ public class LaunchProvider extends DependencyProvider { .property("log4j2.formatMsgNoLookups", "true") .property("client", "java.library.path", getExtension().getMinecraftProvider().nativesDir().getAbsolutePath()) - .property("client", "org.lwjgl.librarypath", getExtension().getMinecraftProvider().nativesDir().getAbsolutePath()) + .property("client", "org.lwjgl.librarypath", getExtension().getMinecraftProvider().nativesDir().getAbsolutePath()); + + if (!getExtension().isForge()) { + launchConfig + .argument("client", "--assetIndex") + .argument("client", getExtension().getMinecraftProvider().getVersionInfo().assetIndex().fabricId(getExtension().getMinecraftProvider().minecraftVersion())) + .argument("client", "--assetsDir") + .argument("client", new File(getDirectories().getUserCache(), "assets").getAbsolutePath()); + } + + if (getExtension().isForge()) { + launchConfig + // Should match YarnNamingService.PATH_TO_MAPPINGS in forge-runtime + .property("fabric.yarnWithSrg.path", getExtension().getMappingsProvider().tinyMappingsWithSrg.toAbsolutePath().toString()) + + .argument("data", "--all") + .argument("data", "--mod") + .argument("data", String.join(",", getExtension().getForge().getDataGenMods())) + .argument("data", "--output") + .argument("data", getProject().file("src/generated/resources").getAbsolutePath()) + + .property("mixin.env.remapRefMap", "true"); + + if (PropertyUtil.getAndFinalize(getExtension().getForge().getUseCustomMixin())) { + launchConfig.property("mixin.forgeloom.inject.mappings.srg-named", getExtension().getMappingsProvider().mixinTinyMappingsWithSrg.toAbsolutePath().toString()); + } else { + launchConfig.property("net.minecraftforge.gradle.GradleStart.srg.srg-mcp", getExtension().getMappingsProvider().srgToNamedSrg.toAbsolutePath().toString()); + } + + Set<String> mixinConfigs = PropertyUtil.getAndFinalize(getExtension().getForge().getMixinConfigs()); + + if (!mixinConfigs.isEmpty()) { + for (String config : mixinConfigs) { + launchConfig.argument("-mixin.config"); + launchConfig.argument(config); + } + } + } + + addDependency(Constants.Dependencies.DEV_LAUNCH_INJECTOR + Constants.Dependencies.Versions.DEV_LAUNCH_INJECTOR, Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES); + addDependency(Constants.Dependencies.TERMINAL_CONSOLE_APPENDER + Constants.Dependencies.Versions.TERMINAL_CONSOLE_APPENDER, Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES); + addDependency(Constants.Dependencies.JETBRAINS_ANNOTATIONS + Constants.Dependencies.Versions.JETBRAINS_ANNOTATIONS, JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME); + + if (getExtension().isForge()) { + addDependency(Constants.Dependencies.FORGE_RUNTIME + Constants.Dependencies.Versions.FORGE_RUNTIME, Constants.Configurations.FORGE_EXTRA); + addDependency(Constants.Dependencies.JAVAX_ANNOTATIONS + Constants.Dependencies.Versions.JAVAX_ANNOTATIONS, JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME); + } + + for (LaunchProviderSettings settings : getExtension().getLaunchConfigs()) { + settings.evaluateNow(); - .argument("client", "--assetIndex") - .argument("client", getExtension().getMinecraftProvider().getVersionInfo().assetIndex().fabricId(getExtension().getMinecraftProvider().minecraftVersion())) - .argument("client", "--assetsDir") - .argument("client", new File(getDirectories().getUserCache(), "assets").getAbsolutePath()); + for (String argument : settings.getArguments()) { + launchConfig.argument(settings.getName(), argument); + } + + for (Map.Entry<String, String> property : settings.getProperties()) { + launchConfig.property(settings.getName(), property.getKey(), property.getValue()); + } + } final boolean plainConsole = getProject().getGradle().getStartParameter().getConsoleOutput() == ConsoleOutput.Plain; final boolean ansiSupportedIDE = new File(getProject().getRootDir(), ".vscode").exists() @@ -81,10 +137,6 @@ public class LaunchProvider extends DependencyProvider { writeLog4jConfig(); FileUtils.writeStringToFile(getDirectories().getDevLauncherConfig(), launchConfig.asString(), StandardCharsets.UTF_8); - addDependency(Constants.Dependencies.DEV_LAUNCH_INJECTOR + Constants.Dependencies.Versions.DEV_LAUNCH_INJECTOR, Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES); - addDependency(Constants.Dependencies.TERMINAL_CONSOLE_APPENDER + Constants.Dependencies.Versions.TERMINAL_CONSOLE_APPENDER, Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES); - addDependency(Constants.Dependencies.JETBRAINS_ANNOTATIONS + Constants.Dependencies.Versions.JETBRAINS_ANNOTATIONS, JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME); - postPopulationScheduler.accept(this::writeRemapClassPath); } @@ -124,6 +176,10 @@ public class LaunchProvider extends DependencyProvider { remapClasspath.add(getExtension().getMinecraftMappedProvider().getIntermediaryJar()); + if (getExtension().isForgeAndNotOfficial()) { + remapClasspath.add(getExtension().getMinecraftMappedProvider().getForgeIntermediaryJar()); + } + String str = remapClasspath.stream() .map(File::getAbsolutePath) .collect(Collectors.joining(File.pathSeparator)); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/MinecraftProviderImpl.java b/src/main/java/net/fabricmc/loom/configuration/providers/MinecraftProviderImpl.java index 4cda3be7..c0ef6acd 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/MinecraftProviderImpl.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/MinecraftProviderImpl.java @@ -37,6 +37,7 @@ import java.util.function.Consumer; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; +import com.google.common.base.Stopwatch; import com.google.common.io.Files; import org.gradle.api.GradleException; import org.gradle.api.Project; @@ -51,6 +52,7 @@ import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.MirrorUtil; import net.fabricmc.loom.util.DownloadUtil; import net.fabricmc.loom.util.HashedDownloadUtil; +import net.fabricmc.loom.util.ZipUtils; import net.fabricmc.stitch.merge.JarMerger; public class MinecraftProviderImpl extends DependencyProvider implements MinecraftProvider { @@ -61,14 +63,16 @@ public class MinecraftProviderImpl extends DependencyProvider implements Minecra private File workingDir; private File minecraftJson; - private File minecraftClientJar; + public File minecraftClientJar; // Note this will be the boostrap jar starting with 21w39a - private File minecraftServerJar; + public File minecraftServerJar; // The extracted server jar from the boostrap, only exists in >=21w39a - private File minecraftExtractedServerJar; + public File minecraftExtractedServerJar; + private Boolean isNewerThan21w39a; private File minecraftMergedJar; private File versionManifestJson; private File experimentalVersionsJson; + private String jarPrefix = ""; public MinecraftProviderImpl(Project project) { super(project); @@ -78,6 +82,10 @@ public class MinecraftProviderImpl extends DependencyProvider implements Minecra public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception { minecraftVersion = dependency.getDependency().getVersion(); + if (getExtension().shouldGenerateSrgTiny() && !getExtension().isForge()) { + addDependency("de.oceanlabs.mcp:mcp_config:" + minecraftVersion, Constants.Configurations.SRG); + } + boolean offline = getProject().getGradle().getStartParameter().isOffline(); initFiles(); @@ -130,6 +138,14 @@ public class MinecraftProviderImpl extends DependencyProvider implements Minecra experimentalVersionsJson = new File(getDirectories().getUserCache(), "experimental_version_manifest.json"); } + public void deleteFiles() { + DownloadUtil.delete(minecraftClientJar); + DownloadUtil.delete(minecraftServerJar); + DownloadUtil.delete(minecraftMergedJar); + DownloadUtil.delete(versionManifestJson); + DownloadUtil.delete(experimentalVersionsJson); + } + private void downloadMcJson(boolean offline) throws IOException { if (getExtension().getShareRemapCaches().get() && !getExtension().isRootProject() && versionManifestJson.exists() && !isRefreshDeps()) { return; @@ -256,11 +272,14 @@ public class MinecraftProviderImpl extends DependencyProvider implements Minecra private void mergeJars(Logger logger) throws IOException { logger.info(":merging jars"); + Stopwatch stopwatch = Stopwatch.createStarted(); try (JarMerger jarMerger = new JarMerger(minecraftClientJar, getServerJarToMerge(logger), minecraftMergedJar)) { jarMerger.enableSyntheticParamsOffset(); jarMerger.merge(); } + + logger.info(":merged jars in " + stopwatch); } private File getServerJarToMerge(Logger logger) throws IOException { @@ -315,6 +334,26 @@ public class MinecraftProviderImpl extends DependencyProvider implements Minecra } } + public File getMinecraftServerJar() { + if (isNewerThan21w39a()) { + try { + return getServerJarToMerge(getProject().getLogger()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + return minecraftServerJar; + } + + public boolean isNewerThan21w39a() { + if (isNewerThan21w39a != null) { + return isNewerThan21w39a; + } + + return isNewerThan21w39a = ZipUtils.contains(minecraftServerJar.toPath(), "META-INF/versions.list"); + } + public File getMergedJar() { return minecraftMergedJar; } @@ -364,6 +403,14 @@ public class MinecraftProviderImpl extends DependencyProvider implements Minecra return libraryProvider; } + public String getJarPrefix() { + return jarPrefix; + } + + public void setJarPrefix(String jarSuffix) { + this.jarPrefix = jarSuffix; + } + @Override public String getTargetConfig() { return Constants.Configurations.MINECRAFT; diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java new file mode 100644 index 00000000..b2ebfa71 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java @@ -0,0 +1,282 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.providers.forge; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; + +import com.google.common.base.Stopwatch; +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Table; +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; +import dev.architectury.refmapremapper.utils.DescriptorRemapper; +import org.gradle.api.Project; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.Opcodes; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.LoomGradlePlugin; +import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; +import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; +import net.fabricmc.loom.util.FileSystemUtil; +import net.fabricmc.loom.util.ThreadingUtils; +import net.fabricmc.loom.util.srg.SrgMerger; +import net.fabricmc.mappingio.MappingReader; +import net.fabricmc.mappingio.format.Tiny2Writer; +import net.fabricmc.mappingio.tree.MappingTree; +import net.fabricmc.mappingio.tree.MappingTreeView; +import net.fabricmc.mappingio.tree.MemoryMappingTree; + +public class FieldMigratedMappingsProvider extends MappingsProviderImpl { + private List<Map.Entry<FieldMember, String>> migratedFields = new ArrayList<>(); + public Path migratedFieldsCache; + public Path rawTinyMappings; + public Path rawTinyMappingsWithSrg; + + public FieldMigratedMappingsProvider(Project project) { + super(project); + } + + @Override + public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception { + LoomGradleExtension extension = getExtension(); + PatchProvider patchProvider = getExtension().getPatchProvider(); + migratedFieldsCache = patchProvider.getProjectCacheFolder().resolve("migrated-fields.json"); + migratedFields.clear(); + + if (LoomGradlePlugin.refreshDeps) { + Files.deleteIfExists(migratedFieldsCache); + } else if (Files.exists(migratedFieldsCache)) { + try (BufferedReader reader = Files.newBufferedReader(migratedFieldsCache)) { + Map<String, String> map = new Gson().fromJson(reader, new TypeToken<Map<String, String>>() { + }.getType()); + migratedFields = new ArrayList<>(); + map.forEach((key, newDescriptor) -> { + String[] split = key.split("#"); + migratedFields.add(new AbstractMap.SimpleEntry<>(new FieldMember(split[0], split[1]), newDescriptor)); + }); + } + } + + super.provide(dependency, postPopulationScheduler); + } + + @Override + protected String createMappingsIdentifier(String mappingsName, String version, String classifier) { + return super.createMappingsIdentifier(mappingsName, version, classifier) + "-forge-" + getExtension().getForgeProvider().getVersion().getCombined(); + } + + @Override + public void manipulateMappings(Path mappingsJar) throws IOException { + Stopwatch stopwatch = Stopwatch.createStarted(); + LoomGradleExtension extension = getExtension(); + this.rawTinyMappings = tinyMappings; + this.rawTinyMappingsWithSrg = tinyMappingsWithSrg; + String mappingsJarName = mappingsJar.getFileName().toString(); + + if (getExtension().shouldGenerateSrgTiny()) { + if (Files.notExists(rawTinyMappingsWithSrg) || isRefreshDeps()) { + // Merge tiny mappings with srg + SrgMerger.mergeSrg(getProject().getLogger(), getExtension().getMappingsProvider()::getMojmapSrgFileIfPossible, getRawSrgFile(), rawTinyMappings, rawTinyMappingsWithSrg, true); + } + } + + tinyMappings = mappingsWorkingDir().resolve("mappings-updated.tiny"); + tinyMappingsWithSrg = mappingsWorkingDir().resolve("mappings-srg-updated.tiny"); + + try { + updateFieldMigration(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + getProject().getLogger().info(":migrated srg fields in " + stopwatch.stop()); + } + + public void updateFieldMigration() throws IOException { + if (!Files.exists(migratedFieldsCache)) { + generateNewFieldMigration(); + Map<String, String> map = new HashMap<>(); + migratedFields.forEach(entry -> { + map.put(entry.getKey().owner + "#" + entry.getKey().field, entry.getValue()); + }); + Files.writeString(migratedFieldsCache, new Gson().toJson(map)); + Files.deleteIfExists(tinyMappings); + } + + if (!Files.exists(tinyMappings)) { + Table<String, String, String> fieldDescriptorMap = HashBasedTable.create(); + + for (Map.Entry<FieldMember, String> entry : migratedFields) { + fieldDescriptorMap.put(entry.getKey().owner, entry.getKey().field, entry.getValue()); + } + + MemoryMappingTree mappings = new MemoryMappingTree(); + + try (BufferedReader reader = Files.newBufferedReader(rawTinyMappings)) { + MappingReader.read(reader, mappings); + + for (MappingTree.ClassMapping classDef : new ArrayList<>(mappings.getClasses())) { + Map<String, String> row = fieldDescriptorMap.row(classDef.getName(MappingsNamespace.INTERMEDIARY.toString())); + + if (!row.isEmpty()) { + for (MappingTree.FieldMapping fieldDef : new ArrayList<>(classDef.getFields())) { + String newDescriptor = row.get(fieldDef.getName(MappingsNamespace.INTERMEDIARY.toString())); + + if (newDescriptor != null) { + fieldDef.setSrcDesc(mappings.mapDesc(newDescriptor, mappings.getNamespaceId(MappingsNamespace.INTERMEDIARY.toString()), MappingTreeView.SRC_NAMESPACE_ID)); + } + } + } + } + } + + StringWriter stringWriter = new StringWriter(); + Tiny2Writer tiny2Writer = new Tiny2Writer(stringWriter, false); + mappings.accept(tiny2Writer); + Files.writeString(tinyMappings, stringWriter.toString(), StandardOpenOption.CREATE); + } + } + + private void generateNewFieldMigration() throws IOException { + Map<FieldMember, String> fieldDescriptorMap = new ConcurrentHashMap<>(); + LoomGradleExtension extension = getExtension(); + ThreadingUtils.TaskCompleter completer = ThreadingUtils.taskCompleter(); + + class Visitor extends ClassVisitor { + private final ThreadLocal<String> lastClass = new ThreadLocal<>(); + + Visitor(int api) { + super(api); + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + lastClass.set(name); + } + + @Override + public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { + fieldDescriptorMap.put(new FieldMember(lastClass.get(), name), descriptor); + return super.visitField(access, name, descriptor, signature, value); + } + } + + Visitor visitor = new Visitor(Opcodes.ASM9); + + for (MinecraftPatchedProvider.Environment environment : MinecraftPatchedProvider.Environment.values()) { + File patchedSrgJar = environment.patchedSrgJar.apply(extension.getMappingsProvider().patchedProvider); + FileSystemUtil.Delegate system = FileSystemUtil.getJarFileSystem(patchedSrgJar, false); + completer.onComplete(value -> system.close()); + + for (Path fsPath : (Iterable<? extends Path>) Files.walk(system.get().getPath("/"))::iterator) { + if (Files.isRegularFile(fsPath) && fsPath.toString().endsWith(".class")) { + completer.add(() -> { + byte[] bytes = Files.readAllBytes(fsPath); + new ClassReader(bytes).accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + }); + } + } + } + + completer.complete(); + Map<FieldMember, String> migratedFields = new HashMap<>(); + + try (BufferedReader reader = Files.newBufferedReader(rawTinyMappingsWithSrg)) { + MemoryMappingTree mappings = new MemoryMappingTree(); + MappingReader.read(reader, mappings); + Map<String, String> srgToIntermediary = new HashMap<>(); + + for (MappingTree.ClassMapping aClass : mappings.getClasses()) { + srgToIntermediary.put(aClass.getName("srg"), aClass.getName("intermediary")); + } + + for (MappingTree.ClassMapping classDef : mappings.getClasses()) { + String ownerSrg = classDef.getName("srg"); + String ownerIntermediary = classDef.getName("intermediary"); + + for (MappingTree.FieldMapping fieldDef : classDef.getFields()) { + String fieldSrg = fieldDef.getName("srg"); + String descriptorSrg = fieldDef.getDesc("srg"); + + FieldMember member = new FieldMember(ownerSrg, fieldSrg); + String newDescriptor = fieldDescriptorMap.get(member); + + if (newDescriptor != null && !newDescriptor.equals(descriptorSrg)) { + String fieldIntermediary = fieldDef.getName("intermediary"); + String descriptorIntermediary = fieldDef.getDesc("intermediary"); + String newDescriptorRemapped = DescriptorRemapper.remapDescriptor(newDescriptor, + clazz -> srgToIntermediary.getOrDefault(clazz, clazz)); + migratedFields.put(new FieldMember(ownerIntermediary, fieldIntermediary), newDescriptorRemapped); + getProject().getLogger().info(ownerIntermediary + "#" + fieldIntermediary + ": " + descriptorIntermediary + " -> " + newDescriptorRemapped); + } + } + } + } + + this.migratedFields.clear(); + this.migratedFields.addAll(migratedFields.entrySet()); + } + + public static class FieldMember { + public String owner; + public String field; + + public FieldMember(String owner, String field) { + this.owner = owner; + this.field = field; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FieldMember that = (FieldMember) o; + return Objects.equals(owner, that.owner) && Objects.equals(field, that.field); + } + + @Override + public int hashCode() { + return Objects.hash(owner, field); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeProvider.java new file mode 100644 index 00000000..9e1e4a7a --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeProvider.java @@ -0,0 +1,115 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2020-2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.providers.forge; + +import java.io.File; +import java.util.function.Consumer; + +import org.gradle.api.Project; + +import net.fabricmc.loom.configuration.DependencyProvider; +import net.fabricmc.loom.util.Constants; + +public class ForgeProvider extends DependencyProvider { + private ForgeVersion version = new ForgeVersion(null); + private File globalCache; + private File projectCache; + + public ForgeProvider(Project project) { + super(project); + } + + @Override + public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception { + version = new ForgeVersion(dependency.getResolvedVersion()); + addDependency(dependency.getDepString() + ":userdev", Constants.Configurations.FORGE_USERDEV); + addDependency(dependency.getDepString() + ":installer", Constants.Configurations.FORGE_INSTALLER); + } + + public ForgeVersion getVersion() { + return version; + } + + public File getGlobalCache() { + if (globalCache == null) { + globalCache = getMinecraftProvider().dir("forge/" + version.getCombined()); + globalCache.mkdirs(); + } + + return globalCache; + } + + public File getProjectCache() { + if (projectCache == null) { + projectCache = new File(getDirectories().getRootProjectPersistentCache(), getMinecraftProvider().minecraftVersion() + "/forge/" + getExtension().getForgeProvider().getVersion().getCombined() + "/project-" + getProject().getPath().replace(':', '@')); + projectCache.mkdirs(); + } + + return projectCache; + } + + @Override + public String getTargetConfig() { + return Constants.Configurations.FORGE; + } + + public static final class ForgeVersion { + private final String combined; + private final String minecraftVersion; + private final String forgeVersion; + + public ForgeVersion(String combined) { + this.combined = combined; + + if (combined == null) { + this.minecraftVersion = "NO_VERSION"; + this.forgeVersion = "NO_VERSION"; + return; + } + + int hyphenIndex = combined.indexOf('-'); + + if (hyphenIndex != -1) { + this.minecraftVersion = combined.substring(0, hyphenIndex); + this.forgeVersion = combined.substring(hyphenIndex + 1); + } else { + this.minecraftVersion = "NO_VERSION"; + this.forgeVersion = combined; + } + } + + public String getCombined() { + return combined; + } + + public String getMinecraftVersion() { + return minecraftVersion; + } + + public String getForgeVersion() { + return forgeVersion; + } + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUniversalProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUniversalProvider.java new file mode 100644 index 00000000..b51299fb --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUniversalProvider.java @@ -0,0 +1,61 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2020-2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.providers.forge; + +import java.io.File; +import java.util.function.Consumer; + +import org.apache.commons.io.FileUtils; +import org.gradle.api.Project; + +import net.fabricmc.loom.configuration.DependencyProvider; +import net.fabricmc.loom.util.Constants; + +public class ForgeUniversalProvider extends DependencyProvider { + private File forge; + + public ForgeUniversalProvider(Project project) { + super(project); + } + + @Override + public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception { + forge = new File(getExtension().getForgeProvider().getGlobalCache(), "forge-universal.jar"); + + if (!forge.exists() || isRefreshDeps()) { + File dep = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve Forge")); + FileUtils.copyFile(dep, forge); + } + } + + public File getForge() { + return forge; + } + + @Override + public String getTargetConfig() { + return Constants.Configurations.FORGE_UNIVERSAL; + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java new file mode 100644 index 00000000..358fd110 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java @@ -0,0 +1,317 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2020-2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.providers.forge; + +import java.io.File; +import java.io.IOException; +import java.io.Reader; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import com.google.common.collect.ImmutableMap; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Dependency; +import org.gradle.api.artifacts.ModuleDependency; +import org.gradle.api.artifacts.transform.InputArtifact; +import org.gradle.api.artifacts.transform.TransformAction; +import org.gradle.api.artifacts.transform.TransformOutputs; +import org.gradle.api.artifacts.transform.TransformParameters; +import org.gradle.api.artifacts.type.ArtifactTypeDefinition; +import org.gradle.api.attributes.Attribute; +import org.gradle.api.file.FileSystemLocation; +import org.gradle.api.provider.Provider; + +import net.fabricmc.loom.configuration.DependencyProvider; +import net.fabricmc.loom.configuration.ide.RunConfigSettings; +import net.fabricmc.loom.configuration.launch.LaunchProviderSettings; +import net.fabricmc.loom.api.ForgeLocalMod; +import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.DependencyDownloader; +import net.fabricmc.loom.util.FileSystemUtil; +import net.fabricmc.loom.util.PropertyUtil; + +public class ForgeUserdevProvider extends DependencyProvider { + private File userdevJar; + private JsonObject json; + private Consumer<Runnable> postPopulationScheduler; + + public ForgeUserdevProvider(Project project) { + super(project); + } + + @Override + public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception { + this.postPopulationScheduler = postPopulationScheduler; + Attribute<Boolean> transformed = Attribute.of("architectury-loom-forge-dependencies-transformed", Boolean.class); + + getProject().getDependencies().registerTransform(RemoveNameProvider.class, spec -> { + spec.getFrom().attribute(transformed, false); + spec.getTo().attribute(transformed, true); + }); + + for (ArtifactTypeDefinition type : getProject().getDependencies().getArtifactTypes()) { + type.getAttributes().attribute(transformed, false); + } + + userdevJar = new File(getExtension().getForgeProvider().getGlobalCache(), "forge-userdev.jar"); + Path configJson = getExtension().getForgeProvider().getGlobalCache().toPath().resolve("forge-config.json"); + + if (!userdevJar.exists() || Files.notExists(configJson) || isRefreshDeps()) { + File resolved = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve Forge userdev")); + Files.copy(resolved.toPath(), userdevJar.toPath(), StandardCopyOption.REPLACE_EXISTING); + + try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + resolved.toURI()), ImmutableMap.of("create", false))) { + Files.copy(fs.getPath("config.json"), configJson, StandardCopyOption.REPLACE_EXISTING); + } + } + + try (Reader reader = Files.newBufferedReader(configJson)) { + json = new Gson().fromJson(reader, JsonObject.class); + } + + addDependency(json.get("mcp").getAsString(), Constants.Configurations.MCP_CONFIG); + addDependency(json.get("mcp").getAsString(), Constants.Configurations.SRG); + addDependency(json.get("universal").getAsString(), Constants.Configurations.FORGE_UNIVERSAL); + + for (JsonElement lib : json.get("libraries").getAsJsonArray()) { + Dependency dep = null; + + if (lib.getAsString().startsWith("org.spongepowered:mixin:")) { + if (PropertyUtil.getAndFinalize(getExtension().getForge().getUseCustomMixin())) { + if (lib.getAsString().contains("0.8.2")) { + dep = addDependency("net.fabricmc:sponge-mixin:0.8.2+build.24", Constants.Configurations.FORGE_DEPENDENCIES); + } else { + dep = addDependency("dev.architectury:mixin-patched" + lib.getAsString().substring(lib.getAsString().lastIndexOf(":")) + ".+", Constants.Configurations.FORGE_DEPENDENCIES); + } + } + } + + if (dep == null) { + dep = addDependency(lib.getAsString(), Constants.Configurations.FORGE_DEPENDENCIES); + } + + if (lib.getAsString().split(":").length < 4) { + ((ModuleDependency) dep).attributes(attributes -> { + attributes.attribute(transformed, true); + }); + } + } + + // TODO: Should I copy the patches from here as well? + // That'd require me to run the "MCP environment" fully up to merging. + for (Map.Entry<String, JsonElement> entry : json.getAsJsonObject("runs").entrySet()) { + LaunchProviderSettings launchSettings = getExtension().getLaunchConfigs().findByName(entry.getKey()); + RunConfigSettings settings = getExtension().getRunConfigs().findByName(entry.getKey()); + JsonObject value = entry.getValue().getAsJsonObject(); + + if (launchSettings != null) { + launchSettings.evaluateLater(() -> { + if (value.has("args")) { + launchSettings.arg(StreamSupport.stream(value.getAsJsonArray("args").spliterator(), false) + .map(JsonElement::getAsString) + .map(this::processTemplates) + .collect(Collectors.toList())); + } + + if (value.has("props")) { + for (Map.Entry<String, JsonElement> props : value.getAsJsonObject("props").entrySet()) { + String string = processTemplates(props.getValue().getAsString()); + + launchSettings.property(props.getKey(), string); + } + } + }); + } + + if (settings != null) { + settings.evaluateLater(() -> { + settings.defaultMainClass(value.getAsJsonPrimitive("main").getAsString()); + + if (value.has("jvmArgs")) { + settings.vmArgs(StreamSupport.stream(value.getAsJsonArray("jvmArgs").spliterator(), false) + .map(JsonElement::getAsString) + .map(this::processTemplates) + .collect(Collectors.toList())); + } + + if (value.has("env")) { + for (Map.Entry<String, JsonElement> env : value.getAsJsonObject("env").entrySet()) { + String string = processTemplates(env.getValue().getAsString()); + + settings.envVariables.put(env.getKey(), string); + } + } + }); + } + } + } + + public abstract static class RemoveNameProvider implements TransformAction<TransformParameters.None> { + @InputArtifact + public abstract Provider<FileSystemLocation> getInput(); + + @Override + public void transform(TransformOutputs outputs) { + try { + File input = getInput().get().getAsFile(); + //architectury-loom-forge-dependencies-transformed + File output = outputs.file(input.getName() + "-alfd-transformed.jar"); + Files.copy(input.toPath(), output.toPath(), StandardCopyOption.REPLACE_EXISTING); + + try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(output, false)) { + Path path = fs.get().getPath("META-INF/services/cpw.mods.modlauncher.api.INameMappingService"); + Files.deleteIfExists(path); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + public String processTemplates(String string) { + if (string.startsWith("{")) { + String key = string.substring(1, string.length() - 1); + + // TODO: Look into ways to not hardcode + if (key.equals("runtime_classpath")) { + string = runtimeClasspath().stream() + .map(File::getAbsolutePath) + .collect(Collectors.joining(File.pathSeparator)); + } else if (key.equals("minecraft_classpath")) { + string = minecraftClasspath().stream() + .map(File::getAbsolutePath) + .collect(Collectors.joining(File.pathSeparator)); + } else if (key.equals("runtime_classpath_file")) { + Path path = getDirectories().getProjectPersistentCache().toPath().resolve("forge_runtime_classpath.txt"); + + postPopulationScheduler.accept(() -> { + try { + Files.writeString(path, runtimeClasspath().stream() + .map(File::getAbsolutePath) + .collect(Collectors.joining("\n")), + StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + + string = path.toAbsolutePath().toString(); + } else if (key.equals("minecraft_classpath_file")) { + Path path = getDirectories().getProjectPersistentCache().toPath().resolve("forge_minecraft_classpath.txt"); + + postPopulationScheduler.accept(() -> { + try { + Files.writeString(path, minecraftClasspath().stream() + .map(File::getAbsolutePath) + .collect(Collectors.joining("\n")), + StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + + string = path.toAbsolutePath().toString(); + } else if (key.equals("asset_index")) { + string = getExtension().getMinecraftProvider().getVersionInfo().assetIndex().fabricId(getExtension().getMinecraftProvider().minecraftVersion()); + } else if (key.equals("assets_root")) { + string = new File(getDirectories().getUserCache(), "assets").getAbsolutePath(); + } else if (key.equals("natives")) { + string = getMinecraftProvider().nativesDir().getAbsolutePath(); + } else if (key.equals("source_roots")) { + List<String> modClasses = new ArrayList<>(); + + for (ForgeLocalMod localMod : getExtension().getForge().getLocalMods()) { + String sourceSetName = localMod.getName(); + + localMod.getSourceSets().flatMap(sourceSet -> Stream.concat( + Stream.of(sourceSet.getOutput().getResourcesDir()), + sourceSet.getOutput().getClassesDirs().getFiles().stream()) + ).map(File::getAbsolutePath).distinct().map(s -> sourceSetName + "%%" + s).collect(Collectors.toCollection(() -> modClasses)); + } + + string = String.join(File.pathSeparator, modClasses); + } else if (key.equals("mcp_mappings")) { + string = "loom.stub"; + } else if (json.has(key)) { + JsonElement element = json.get(key); + + if (element.isJsonArray()) { + string = StreamSupport.stream(element.getAsJsonArray().spliterator(), false) + .map(JsonElement::getAsString) + .flatMap(str -> { + if (str.contains(":")) { + return DependencyDownloader.download(getProject(), str, false, false).getFiles().stream() + .map(File::getAbsolutePath) + .filter(dep -> !dep.contains("bootstraplauncher")); // TODO: Hack + } + + return Stream.of(str); + }) + .collect(Collectors.joining(File.pathSeparator)); + } else { + string = element.toString(); + } + } else { + getProject().getLogger().warn("Unrecognized template! " + string); + } + } + + return string; + } + + private Set<File> runtimeClasspath() { + // Should we actually include the runtime classpath here? Forge doesn't seem to be using this property anyways + return minecraftClasspath(); + } + + private Set<File> minecraftClasspath() { + return DependencyDownloader.resolveFiles(getProject().getConfigurations().getByName(Constants.Configurations.FORGE_RUNTIME_LIBRARY), true); + } + + public File getUserdevJar() { + return userdevJar; + } + + @Override + public String getTargetConfig() { + return Constants.Configurations.FORGE_USERDEV; + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java new file mode 100644 index 00000000..7404cc0a --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java @@ -0,0 +1,232 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2020-2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.providers.forge; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.Reader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; +import java.util.List; +import java.util.function.Consumer; +import java.util.jar.Attributes; +import java.util.jar.Manifest; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import org.gradle.api.Project; +import org.gradle.api.file.FileCollection; + +import net.fabricmc.loom.configuration.DependencyProvider; +import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.DependencyDownloader; +import net.fabricmc.loom.util.ZipUtils; + +public class McpConfigProvider extends DependencyProvider { + private Path mcp; + private Path configJson; + private Path mappings; + private Boolean official; + private String mappingsPath; + private RemapAction remapAction; + + public McpConfigProvider(Project project) { + super(project); + } + + @Override + public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception { + init(dependency.getDependency().getVersion()); + + Path mcpZip = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve MCPConfig")).toPath(); + + if (!Files.exists(mcp) || !Files.exists(configJson) || isRefreshDeps()) { + Files.copy(mcpZip, mcp, StandardCopyOption.REPLACE_EXISTING); + Files.write(configJson, ZipUtils.unpack(mcp, "config.json"), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + } + + JsonObject json; + + try (Reader reader = Files.newBufferedReader(configJson)) { + json = new Gson().fromJson(reader, JsonObject.class); + } + + official = json.has("official") && json.getAsJsonPrimitive("official").getAsBoolean(); + mappingsPath = json.get("data").getAsJsonObject().get("mappings").getAsString(); + + if (json.has("functions")) { + JsonObject functions = json.getAsJsonObject("functions"); + + if (functions.has("rename")) { + remapAction = new ConfigDefinedRemapAction(getProject(), functions.getAsJsonObject("rename")); + } + } + + if (remapAction == null) { + throw new RuntimeException("Could not find remap action, this is probably a version Architectury Loom does not support!"); + } + } + + public RemapAction getRemapAction() { + return remapAction; + } + + private void init(String version) throws IOException { + Path dir = getMinecraftProvider().dir("mcp/" + version).toPath(); + mcp = dir.resolve("mcp.zip"); + configJson = dir.resolve("mcp-config.json"); + mappings = dir.resolve("mcp-config-mappings.txt"); + + if (isRefreshDeps()) { + Files.deleteIfExists(mappings); + } + } + + public Path getMappings() { + if (Files.notExists(mappings)) { + try { + Files.write(mappings, ZipUtils.unpack(getMcp(), getMappingsPath()), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + } catch (IOException e) { + throw new IllegalStateException("Failed to find mappings '" + getMappingsPath() + "' in " + getMcp() + "!"); + } + } + + return mappings; + } + + public Path getMcp() { + return mcp; + } + + public boolean isOfficial() { + return official; + } + + public String getMappingsPath() { + return mappingsPath; + } + + @Override + public String getTargetConfig() { + return Constants.Configurations.MCP_CONFIG; + } + + public interface RemapAction { + FileCollection getClasspath(); + + String getMainClass(); + + List<String> getArgs(Path input, Path output, Path mappings, FileCollection libraries); + } + + public static class ConfigDefinedRemapAction implements RemapAction { + private final Project project; + private final String name; + private final File mainClasspath; + private final FileCollection classpath; + private final List<String> args; + private boolean hasLibraries; + + public ConfigDefinedRemapAction(Project project, JsonObject json) { + this.project = project; + this.name = json.get("version").getAsString(); + this.mainClasspath = DependencyDownloader.download(project, this.name, false, true) + .getSingleFile(); + this.classpath = DependencyDownloader.download(project, this.name, true, true); + this.args = StreamSupport.stream(json.getAsJsonArray("args").spliterator(), false) + .map(JsonElement::getAsString) + .collect(Collectors.toList()); + for (int i = 1; i < this.args.size(); i++) { + if (this.args.get(i).equals("{libraries}")) { + this.args.remove(i); + this.args.remove(i - 1); + this.hasLibraries = true; + break; + } + } + } + + @Override + public FileCollection getClasspath() { + return classpath; + } + + @Override + public String getMainClass() { + try { + byte[] manifestBytes = ZipUtils.unpackNullable(mainClasspath.toPath(), "META-INF/MANIFEST.MF"); + + if (manifestBytes == null) { + throw new RuntimeException("Could not find MANIFEST.MF in " + mainClasspath + "!"); + } + + Manifest manifest = new Manifest(new ByteArrayInputStream(manifestBytes)); + Attributes attributes = manifest.getMainAttributes(); + String value = attributes.getValue(Attributes.Name.MAIN_CLASS); + + if (value == null) { + throw new RuntimeException("Could not find main class in " + mainClasspath + "!"); + } else { + return value; + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public List<String> getArgs(Path input, Path output, Path mappings, FileCollection libraries) { + List<String> args = this.args.stream() + .map(str -> { + return switch (str) { + case "{input}" -> input.toAbsolutePath().toString(); + case "{output}" -> output.toAbsolutePath().toString(); + case "{mappings}" -> mappings.toAbsolutePath().toString(); + default -> str; + }; + }) + .collect(Collectors.toList()); + + if (hasLibraries) { + for (File file : libraries) { + args.add("-e=" + file.getAbsolutePath()); + } + } + + return args; + } + + @Override + public String toString() { + return this.name; + } + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java new file mode 100644 index 00000000..fadf7d33 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java @@ -0,0 +1,786 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2020-2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.providers.forge; + +import java.io.ByteArrayInputStream; +import java.io.Closeable; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.UncheckedIOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.jar.Attributes; +import java.util.jar.Manifest; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import com.google.common.base.Preconditions; +import com.google.common.base.Stopwatch; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.hash.Hashing; +import com.google.common.io.ByteSource; +import de.oceanlabs.mcp.mcinjector.adaptors.ParameterAnnotationFixer; +import dev.architectury.tinyremapper.InputTag; +import dev.architectury.tinyremapper.OutputConsumerPath; +import dev.architectury.tinyremapper.TinyRemapper; +import net.minecraftforge.binarypatcher.ConsoleTool; +import org.apache.commons.io.output.NullOutputStream; +import org.gradle.api.Project; +import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.file.FileCollection; +import org.gradle.api.logging.LogLevel; +import org.gradle.api.logging.Logger; +import org.gradle.api.logging.configuration.ShowStacktrace; +import org.gradle.api.plugins.JavaPluginConvention; +import org.gradle.api.tasks.SourceSet; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.ClassNode; + +import net.fabricmc.loom.configuration.DependencyProvider; +import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl; +import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.DependencyDownloader; +import net.fabricmc.loom.util.FileSystemUtil; +import net.fabricmc.loom.util.MappingsProviderVerbose; +import net.fabricmc.loom.util.ThreadingUtils; +import net.fabricmc.loom.util.TinyRemapperHelper; +import net.fabricmc.loom.util.ZipUtils; +import net.fabricmc.loom.util.function.FsPathConsumer; +import net.fabricmc.loom.util.srg.InnerClassRemapper; +import net.fabricmc.loom.util.srg.SpecialSourceExecutor; +import net.fabricmc.mappingio.tree.MemoryMappingTree; + +public class MinecraftPatchedProvider extends DependencyProvider { + private static final String LOOM_PATCH_VERSION_KEY = "Loom-Patch-Version"; + private static final String CURRENT_LOOM_PATCH_VERSION = "5"; + private static final String NAME_MAPPING_SERVICE_PATH = "/inject/META-INF/services/cpw.mods.modlauncher.api.INameMappingService"; + + // Step 1: Remap Minecraft to SRG (global) + private File minecraftClientSrgJar; + private File minecraftServerSrgJar; + // Step 2: Binary Patch (global) + private File minecraftClientPatchedSrgJar; + private File minecraftServerPatchedSrgJar; + // Step 3: Merge (global) + private File minecraftMergedPatchedSrgJar; + // Step 4: Access Transform (global or project) + private File minecraftMergedPatchedSrgAtJar; + // Step 5: Remap Patched AT & Forge to Official (global or project) + private File minecraftMergedPatchedJar; + private File forgeMergedJar; + private File minecraftClientExtra; + + private File projectAtHash; + private Set<File> projectAts = new HashSet<>(); + private boolean atDirty = false; + private boolean filesDirty = false; + private Path mcpConfigMappings; + private Path[] mergedMojangTsrg2Files; + + public MinecraftPatchedProvider(Project project) { + super(project); + } + + public void initFiles() throws IOException { + filesDirty = false; + projectAtHash = new File(getDirectories().getProjectPersistentCache(), "at.sha256"); + ConfigurableFileCollection accessTransformers = getExtension().getForge().getAccessTransformers(); + accessTransformers.finalizeValue(); + projectAts = accessTransformers.getFiles(); + + if (projectAts.isEmpty()) { + SourceSet main = getProject().getConvention().findPlugin(JavaPluginConvention.class).getSourceSets().getByName("main"); + + for (File srcDir : main.getResources().getSrcDirs()) { + File projectAt = new File(srcDir, Constants.Forge.ACCESS_TRANSFORMER_PATH); + + if (projectAt.exists()) { + this.projectAts.add(projectAt); + break; + } + } + } + + if (isRefreshDeps() || !projectAtHash.exists()) { + writeAtHash(); + atDirty = !projectAts.isEmpty(); + } else { + byte[] expected = com.google.common.io.Files.asByteSource(projectAtHash).read(); + byte[] current = getProjectAtsHash(); + boolean mismatched = !Arrays.equals(current, expected); + + if (mismatched) { + writeAtHash(); + } + + atDirty = mismatched; + } + + MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); + PatchProvider patchProvider = getExtension().getPatchProvider(); + String minecraftVersion = minecraftProvider.minecraftVersion(); + String patchId = "forge-" + getExtension().getForgeProvider().getVersion().getCombined() + "-"; + + if (getExtension().isForgeAndOfficial()) { + minecraftProvider.setJarPrefix(patchId); + } + + File globalCache = getExtension().getForgeProvider().getGlobalCache(); + File projectDir = usesProjectCache() ? getExtension().getForgeProvider().getProjectCache() : globalCache; + projectDir.mkdirs(); + + minecraftClientSrgJar = new File(globalCache, "minecraft-client-srg.jar"); + minecraftServerSrgJar = new File(globalCache, "minecraft-server-srg.jar"); + minecraftClientPatchedSrgJar = new File(globalCache, "client-srg-patched.jar"); + minecraftServerPatchedSrgJar = new File(globalCache, "server-srg-patched.jar"); + minecraftMergedPatchedSrgJar = new File(globalCache, "merged-srg-patched.jar"); + forgeMergedJar = getExtension().isForgeAndOfficial() ? null : new File(globalCache, "forge-official.jar"); + minecraftMergedPatchedSrgAtJar = new File(projectDir, "merged-srg-at-patched.jar"); + minecraftMergedPatchedJar = new File(projectDir, "merged-patched.jar"); + minecraftClientExtra = new File(globalCache, "forge-client-extra.jar"); + + if (isRefreshDeps() || Stream.of(getGlobalCaches()).anyMatch(((Predicate<File>) File::exists).negate()) + || !isPatchedJarUpToDate(minecraftMergedPatchedJar)) { + cleanAllCache(); + } else if (atDirty || Stream.of(getProjectCache()).anyMatch(((Predicate<File>) File::exists).negate())) { + cleanProjectCache(); + } + } + + private byte[] getProjectAtsHash() throws IOException { + if (projectAts.isEmpty()) return ByteSource.empty().hash(Hashing.sha256()).asBytes(); + List<ByteSource> currentBytes = new ArrayList<>(); + + for (File projectAt : projectAts) { + currentBytes.add(com.google.common.io.Files.asByteSource(projectAt)); + } + + return ByteSource.concat(currentBytes).hash(Hashing.sha256()).asBytes(); + } + + public void cleanAllCache() { + for (File file : getGlobalCaches()) { + file.delete(); + } + + cleanProjectCache(); + } + + private File[] getGlobalCaches() { + File[] files = { + minecraftClientSrgJar, + minecraftServerSrgJar, + minecraftClientPatchedSrgJar, + minecraftServerPatchedSrgJar, + minecraftMergedPatchedSrgJar, + minecraftClientExtra, + }; + + if (forgeMergedJar != null) { + Arrays.copyOf(files, files.length + 1); + files[files.length - 1] = forgeMergedJar; + } + + return files; + } + + public void cleanProjectCache() { + for (File file : getProjectCache()) { + file.delete(); + } + } + + private File[] getProjectCache() { + return new File[] { + minecraftMergedPatchedSrgAtJar, + minecraftMergedPatchedJar + }; + } + + private boolean dirty; + + @Override + public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception { + initFiles(); + + if (atDirty) { + getProject().getLogger().lifecycle(":found dirty access transformers"); + } + + this.dirty = false; + + if (!minecraftClientSrgJar.exists() || !minecraftServerSrgJar.exists()) { + this.dirty = true; + // Remap official jars to MCPConfig remapped srg jars + createSrgJars(getProject().getLogger()); + } + + if (!minecraftClientPatchedSrgJar.exists() || !minecraftServerPatchedSrgJar.exists()) { + this.dirty = true; + patchJars(getProject().getLogger()); + } + } + + public void finishProvide() throws Exception { + if (dirty || !minecraftMergedPatchedSrgJar.exists()) { + mergeJars(getProject().getLogger()); + } + + if (atDirty || !minecraftMergedPatchedSrgAtJar.exists()) { + this.dirty = true; + accessTransformForge(getProject().getLogger()); + } + + if (forgeMergedJar != null && !forgeMergedJar.exists()) { + this.dirty = true; + } + + if (dirty) { + remapPatchedJar(getProject().getLogger()); + + if (getExtension().isForgeAndOfficial()) { + fillClientExtraJar(); + } + } + + this.filesDirty = dirty; + this.dirty = false; + + if (getExtension().isForgeAndOfficial()) { + addDependency(minecraftClientExtra, Constants.Configurations.FORGE_EXTRA); + } + } + + private void fillClientExtraJar() throws IOException { + Files.deleteIfExists(minecraftClientExtra.toPath()); + FileSystemUtil.getJarFileSystem(minecraftClientExtra, true).close(); + + copyNonClassFiles(getExtension().getMinecraftProvider().minecraftClientJar, minecraftClientExtra); + } + + private TinyRemapper buildRemapper(Path input) throws IOException { + Path[] libraries = TinyRemapperHelper.getMinecraftDependencies(getProject()); + MemoryMappingTree mappingsWithSrg = getExtension().getMappingsProvider().getMappingsWithSrg(); + + TinyRemapper remapper = TinyRemapper.newRemapper() + .logger(getProject().getLogger()::lifecycle) + .logUnknownInvokeDynamic(false) + .withMappings(TinyRemapperHelper.create(mappingsWithSrg, "srg", "official", true)) + .withMappings(InnerClassRemapper.of(InnerClassRemapper.readClassNames(input), mappingsWithSrg, "srg", "official")) + .renameInvalidLocals(true) + .rebuildSourceFilenames(true) + .fixPackageAccess(true) + .build(); + + if (getProject().getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0) { + MappingsProviderVerbose.saveFile(remapper); + } + + remapper.readClassPath(libraries); + remapper.prepareClasses(); + return remapper; + } + + private void writeAtHash() throws IOException { + try (FileOutputStream out = new FileOutputStream(projectAtHash)) { + out.write(getProjectAtsHash()); + } + } + + private void createSrgJars(Logger logger) throws Exception { + MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); + produceSrgJar(getExtension().isForgeAndOfficial(), minecraftProvider.minecraftClientJar.toPath(), minecraftProvider.getMinecraftServerJar().toPath()); + } + + private void produceSrgJar(boolean official, Path clientJar, Path serverJar) throws IOException { + Path tmpSrg = getToSrgMappings(); + Set<File> mcLibs = getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES).resolve(); + + ThreadingUtils.run(() -> { + Files.copy(SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "client", mcLibs, clientJar, tmpSrg), minecraftClientSrgJar.toPath()); + }, () -> { + Files.copy(SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "server", mcLibs, serverJar, tmpSrg), minecraftServerSrgJar.toPath()); + }); + } + + private Path getToSrgMappings() throws IOException { + if (getExtension().getSrgProvider().isTsrgV2()) { + return getExtension().getSrgProvider().getMergedMojangRaw(); + } else { + return getExtension().getMcpConfigProvider().getMappings(); + } + } + + private void fixParameterAnnotation(File jarFile) throws Exception { + getProject().getLogger().info(":fixing parameter annotations for " + jarFile.getAbsolutePath()); + Stopwatch stopwatch = Stopwatch.createStarted(); + + try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + jarFile.toURI()), ImmutableMap.of("create", false))) { + ThreadingUtils.TaskCompleter completer = ThreadingUtils.taskCompleter(); + + for (Path file : (Iterable<? extends Path>) Files.walk(fs.getPath("/"))::iterator) { + if (!file.toString().endsWith(".class")) continue; + + completer.add(() -> { + byte[] bytes = Files.readAllBytes(file); + ClassReader reader = new ClassReader(bytes); + ClassNode node = new ClassNode(); + ClassVisitor visitor = new ParameterAnnotationFixer(node, null); + reader.accept(visitor, 0); + + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); + node.accept(writer); + byte[] out = writer.toByteArray(); + + if (!Arrays.equals(bytes, out)) { + Files.delete(file); + Files.write(file, out); + } + }); + } + + completer.complete(); + } + + getProject().getLogger().info(":fixing parameter annotations for " + jarFile.getAbsolutePath() + " in " + stopwatch); + } + + private void deleteParameterNames(File jarFile) throws Exception { + getProject().getLogger().info(":deleting parameter names for " + jarFile.getAbsolutePath()); + Stopwatch stopwatch = Stopwatch.createStarted(); + + try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + jarFile.toURI()), ImmutableMap.of("create", false))) { + ThreadingUtils.TaskCompleter completer = ThreadingUtils.taskCompleter(); + Pattern vignetteParameters = Pattern.compile("p_[0-9a-zA-Z]+_(?:[0-9a-zA-Z]+_)?"); + + for (Path file : (Iterable<? extends Path>) Files.walk(fs.getPath("/"))::iterator) { + if (!file.toString().endsWith(".class")) continue; + + completer.add(() -> { + byte[] bytes = Files.readAllBytes(file); + ClassReader reader = new ClassReader(bytes); + ClassWriter writer = new ClassWriter(0); + + reader.accept(new ClassVisitor(Opcodes.ASM9, writer) { + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + return new MethodVisitor(Opcodes.ASM9, super.visitMethod(access, name, descriptor, signature, exceptions)) { + @Override + public void visitParameter(String name, int access) { + if (vignetteParameters.matcher(name).matches()) { + super.visitParameter(null, access); + } else { + super.visitParameter(name, access); + } + } + + @Override + public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) { + if (!vignetteParameters.matcher(name).matches()) { + super.visitLocalVariable(name, descriptor, signature, start, end, index); + } + } + }; + } + }, 0); + + byte[] out = writer.toByteArray(); + + if (!Arrays.equals(bytes, out)) { + Files.delete(file); + Files.write(file, out); + } + }); + } + + completer.complete(); + } + + getProject().getLogger().info(":deleting parameter names for " + jarFile.getAbsolutePath() + " in " + stopwatch); + } + + private File getForgeJar() { + return getExtension().getForgeUniversalProvider().getForge(); + } + + private File getForgeUserdevJar() { + return getExtension().getForgeUserdevProvider().getUserdevJar(); + } + + private boolean isPatchedJarUpToDate(File jar) throws IOException { + if (!jar.exists()) return false; + + byte[] manifestBytes = ZipUtils.unpackNullable(jar.toPath(), "META-INF/MANIFEST.MF"); + + if (manifestBytes == null) { + return false; + } + + Manifest manifest = new Manifest(new ByteArrayInputStream(manifestBytes)); + Attributes attributes = manifest.getMainAttributes(); + String value = attributes.getValue(LOOM_PATCH_VERSION_KEY); + + if (Objects.equals(value, CURRENT_LOOM_PATCH_VERSION)) { + return true; + } else { + getProject().getLogger().lifecycle(":forge patched jars not up to date. current version: " + value); + return false; + } + } + + private void accessTransformForge(Logger logger) throws Exception { + MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); + List<File> toDelete = new ArrayList<>(); + String atDependency = Constants.Dependencies.ACCESS_TRANSFORMERS + (minecraftProvider.isNewerThan21w39a() ? Constants.Dependencies.Versions.ACCESS_TRANSFORMERS_NEW : Constants.Dependencies.Versions.ACCESS_TRANSFORMERS); + FileCollection classpath = DependencyDownloader.download(getProject(), atDependency); + Stopwatch stopwatch = Stopwatch.createStarted(); + + logger.lifecycle(":access transforming minecraft"); + + File input = minecraftMergedPatchedSrgJar; + File target = minecraftMergedPatchedSrgAtJar; + Files.deleteIfExists(target.toPath()); + + List<String> args = new ArrayList<>(); + args.add("--inJar"); + args.add(input.getAbsolutePath()); + args.add("--outJar"); + args.add(target.getAbsolutePath()); + + for (File jar : ImmutableList.of(getForgeJar(), getForgeUserdevJar(), minecraftMergedPatchedSrgJar)) { + byte[] atBytes = ZipUtils.unpackNullable(jar.toPath(), Constants.Forge.ACCESS_TRANSFORMER_PATH); + + if (atBytes != null) { + File tmpFile = File.createTempFile("at-conf", ".cfg"); + toDelete.add(tmpFile); + Files.write(tmpFile.toPath(), atBytes, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + args.add("--atFile"); + args.add(tmpFile.getAbsolutePath()); + } + } + + if (usesProjectCache()) { + for (File projectAt : projectAts) { + args.add("--atFile"); + args.add(projectAt.getAbsolutePath()); + } + } + + getProject().javaexec(spec -> { + spec.getMainClass().set("net.minecraftforge.accesstransformer.TransformerProcessor"); + spec.setArgs(args); + spec.setClasspath(classpath); + + // if running with INFO or DEBUG logging + if (getProject().getGradle().getStartParameter().getShowStacktrace() != ShowStacktrace.INTERNAL_EXCEPTIONS + || getProject().getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0) { + spec.setStandardOutput(System.out); + spec.setErrorOutput(System.err); + } else { + spec.setStandardOutput(NullOutputStream.NULL_OUTPUT_STREAM); + spec.setErrorOutput(NullOutputStream.NULL_OUTPUT_STREAM); + } + }).rethrowFailure().assertNormalExitValue(); + + for (File file : toDelete) { + file.delete(); + } + + logger.lifecycle(":access transformed minecraft in " + stopwatch.stop()); + } + + public enum Environment { + CLIENT(provider -> provider.minecraftClientSrgJar, + provider -> provider.minecraftClientPatchedSrgJar + ), + SERVER(provider -> provider.minecraftServerSrgJar, + provider -> provider.minecraftServerPatchedSrgJar + ); + + final Function<MinecraftPatchedProvider, File> srgJar; + final Function<MinecraftPatchedProvider, File> patchedSrgJar; + + Environment(Function<MinecraftPatchedProvider, File> srgJar, + Function<MinecraftPatchedProvider, File> patchedSrgJar) { + this.srgJar = srgJar; + this.patchedSrgJar = patchedSrgJar; + } + + public String side() { + return name().toLowerCase(Locale.ROOT); + } + } + + private void remapPatchedJar(Logger logger) throws Exception { + getProject().getLogger().lifecycle(":remapping minecraft (TinyRemapper, srg -> official)"); + Path mcInput = minecraftMergedPatchedSrgAtJar.toPath(); + Path mcOutput = minecraftMergedPatchedJar.toPath(); + Path forgeJar = getForgeJar().toPath(); + Path forgeUserdevJar = getForgeUserdevJar().toPath(); + Path forgeOutput = null; + Files.deleteIfExists(mcOutput); + boolean splitJars = forgeMergedJar != null; + + if (splitJars) { + forgeOutput = forgeMergedJar.toPath(); + Files.deleteIfExists(forgeOutput); + } + + TinyRemapper remapper = buildRemapper(mcInput); + + try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(mcOutput).build(); + Closeable outputConsumerForge = !splitJars ? () -> { + } : new OutputConsumerPath.Builder(forgeOutput).build()) { + outputConsumer.addNonClassFiles(mcInput); + + InputTag mcTag = remapper.createInputTag(); + InputTag forgeTag = remapper.createInputTag(); + List<CompletableFuture<?>> futures = new ArrayList<>(); + futures.add(remapper.readInputsAsync(mcTag, mcInput)); + futures.add(remapper.readInputsAsync(forgeTag, forgeJar, forgeUserdevJar)); + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); + remapper.apply(outputConsumer, mcTag); + remapper.apply(splitJars ? (OutputConsumerPath) outputConsumerForge : outputConsumer, forgeTag); + } finally { + remapper.finish(); + } + + copyNonClassFiles(forgeJar.toFile(), splitJars ? forgeMergedJar : minecraftMergedPatchedJar); + copyUserdevFiles(forgeUserdevJar.toFile(), splitJars ? forgeMergedJar : minecraftMergedPatchedJar); + applyLoomPatchVersion(mcOutput); + } + + private void patchJars(Logger logger) throws IOException { + Stopwatch stopwatch = Stopwatch.createStarted(); + logger.lifecycle(":patching jars"); + + PatchProvider patchProvider = getExtension().getPatchProvider(); + patchJars(minecraftClientSrgJar, minecraftClientPatchedSrgJar, patchProvider.clientPatches); + patchJars(minecraftServerSrgJar, minecraftServerPatchedSrgJar, patchProvider.serverPatches); + + ThreadingUtils.run(Environment.values(), environment -> { + copyMissingClasses(environment.srgJar.apply(this), environment.patchedSrgJar.apply(this)); + deleteParameterNames(environment.patchedSrgJar.apply(this)); + + if (getExtension().isForgeAndNotOfficial()) { + fixParameterAnnotation(environment.patchedSrgJar.apply(this)); + } + }); + + logger.lifecycle(":patched jars in " + stopwatch.stop()); + } + + private void patchJars(File clean, File output, Path patches) throws IOException { + PrintStream previous = System.out; + + try { + System.setOut(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM)); + } catch (SecurityException ignored) { + // Failed to replace logger filter, just ignore + } + + ConsoleTool.main(new String[] { + "--clean", clean.getAbsolutePath(), + "--output", output.getAbsolutePath(), + "--apply", patches.toAbsolutePath().toString() + }); + + try { + System.setOut(previous); + } catch (SecurityException ignored) { + // Failed to replace logger filter, just ignore + } + } + + private void mergeJars(Logger logger) throws IOException { + // FIXME: Hack here: There are no server-only classes so we can just copy the client JAR. + // This will change if upstream Loom adds the possibility for separate projects/source sets per environment. + Files.copy(minecraftClientPatchedSrgJar.toPath(), minecraftMergedPatchedSrgJar.toPath()); + + logger.lifecycle(":copying resources"); + + // Copy resources + if (getExtension().isForgeAndNotOfficial()) { + // Copy resources + MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); + copyNonClassFiles(minecraftProvider.minecraftClientJar, minecraftMergedPatchedSrgJar); + copyNonClassFiles(minecraftProvider.getMinecraftServerJar(), minecraftMergedPatchedSrgJar); + } + } + + private void walkFileSystems(File source, File target, Predicate<Path> filter, Function<FileSystem, Iterable<Path>> toWalk, FsPathConsumer action) + throws IOException { + try (FileSystemUtil.Delegate sourceFs = FileSystemUtil.getJarFileSystem(source, false); + FileSystemUtil.Delegate targetFs = FileSystemUtil.getJarFileSystem(target, false)) { + for (Path sourceDir : toWalk.apply(sourceFs.get())) { + Path dir = sourceDir.toAbsolutePath(); + if (!Files.exists(dir)) continue; + Files.walk(dir) + .filter(Files::isRegularFile) + .filter(filter) + .forEach(it -> { + boolean root = dir.getParent() == null; + + try { + Path relativeSource = root ? it : dir.relativize(it); + Path targetPath = targetFs.get().getPath(relativeSource.toString()); + action.accept(sourceFs.get(), targetFs.get(), it, targetPath); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } + } + } + + private void walkFileSystems(File source, File target, Predicate<Path> filter, FsPathConsumer action) throws IOException { + walkFileSystems(source, target, filter, FileSystem::getRootDirectories, action); + } + + private void copyAll(File source, File target) throws IOException { + walkFileSystems(source, target, it -> true, this::copyReplacing); + } + + private void copyMissingClasses(File source, File target) throws IOException { + walkFileSystems(source, target, it -> it.toString().endsWith(".class"), (sourceFs, targetFs, sourcePath, targetPath) -> { + if (Files.exists(targetPath)) return; + Path parent = targetPath.getParent(); + + if (parent != null) { + Files.createDirectories(parent); + } + + Files.copy(sourcePath, targetPath); + }); + } + + private void copyNonClassFiles(File source, File target) throws IOException { + Predicate<Path> filter = getExtension().isForgeAndOfficial() ? file -> { + String s = file.toString(); + return !s.endsWith(".class"); + } : file -> { + String s = file.toString(); + return !s.endsWith(".class") || (s.startsWith("META-INF") && !s.startsWith("META-INF/services")); + }; + + walkFileSystems(source, target, filter, this::copyReplacing); + } + + private void copyReplacing(FileSystem sourceFs, FileSystem targetFs, Path sourcePath, Path targetPath) throws IOException { + Path parent = targetPath.getParent(); + + if (parent != null) { + Files.createDirectories(parent); + } + + Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING); + } + + private void copyUserdevFiles(File source, File target) throws IOException { + // Removes the Forge name mapping service definition so that our own is used. + // If there are multiple name mapping services with the same "understanding" pair + // (source -> target namespace pair), modlauncher throws a fit and will crash. + // To use our YarnNamingService instead of MCPNamingService, we have to remove this file. + Predicate<Path> filter = file -> !file.toString().endsWith(".class") && !file.toString().equals(NAME_MAPPING_SERVICE_PATH); + + walkFileSystems(source, target, filter, fs -> Collections.singleton(fs.getPath("inject")), (sourceFs, targetFs, sourcePath, targetPath) -> { + Path parent = targetPath.getParent(); + + if (parent != null) { + Files.createDirectories(parent); + } + + Files.copy(sourcePath, targetPath); + }); + } + + public void applyLoomPatchVersion(Path target) throws IOException { + try (FileSystemUtil.Delegate delegate = FileSystemUtil.getJarFileSystem(target, false)) { + Path manifestPath = delegate.get().getPath("META-INF/MANIFEST.MF"); + + Preconditions.checkArgument(Files.exists(manifestPath), "META-INF/MANIFEST.MF does not exist in patched srg jar!"); + Manifest manifest = new Manifest(); + + if (Files.exists(manifestPath)) { + try (InputStream stream = Files.newInputStream(manifestPath)) { + manifest.read(stream); + manifest.getMainAttributes().putValue(LOOM_PATCH_VERSION_KEY, CURRENT_LOOM_PATCH_VERSION); + } + } + + try (OutputStream stream = Files.newOutputStream(manifestPath, StandardOpenOption.CREATE)) { + manifest.write(stream); + } + } + } + + public File getMergedJar() { + return minecraftMergedPatchedJar; + } + + public File getForgeMergedJar() { + return forgeMergedJar; + } + + public boolean usesProjectCache() { + return !projectAts.isEmpty(); + } + + public boolean isAtDirty() { + return atDirty || filesDirty; + } + + @Override + public String getTargetConfig() { + return Constants.Configurations.MINECRAFT; + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java new file mode 100644 index 00000000..d49ae8b4 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java @@ -0,0 +1,88 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2020-2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.providers.forge; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.function.Consumer; + +import com.google.common.collect.ImmutableMap; +import org.gradle.api.Project; + +import net.fabricmc.loom.configuration.DependencyProvider; +import net.fabricmc.loom.util.Constants; + +public class PatchProvider extends DependencyProvider { + public Path clientPatches; + public Path serverPatches; + public Path projectCacheFolder; + + public PatchProvider(Project project) { + super(project); + } + + @Override + public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception { + init(dependency.getDependency().getVersion()); + + if (Files.notExists(clientPatches) || Files.notExists(serverPatches) || isRefreshDeps()) { + getProject().getLogger().info(":extracting forge patches"); + + Path installerJar = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve Forge installer")).toPath(); + + try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + installerJar.toUri()), ImmutableMap.of("create", false))) { + Files.copy(fs.getPath("data", "client.lzma"), clientPatches, StandardCopyOption.REPLACE_EXISTING); + Files.copy(fs.getPath("data", "server.lzma"), serverPatches, StandardCopyOption.REPLACE_EXISTING); + } + } + } + + private void init(String forgeVersion) { + projectCacheFolder = getMinecraftProvider().dir("forge/" + forgeVersion).toPath(); + clientPatches = projectCacheFolder.resolve("patches-client.lzma"); + serverPatches = projectCacheFolder.resolve("patches-server.lzma"); + + try { + Files.createDirectories(projectCacheFolder); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public Path getProjectCacheFolder() { + return projectCacheFolder; + } + + @Override + public String getTargetConfig() { + return Constants.Configurations.FORGE_INSTALLER; + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java new file mode 100644 index 00000000..32a12dc6 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java @@ -0,0 +1,273 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2020-2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.providers.forge; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.io.StringReader; +import java.io.UncheckedIOException; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +import com.google.common.base.Stopwatch; +import com.google.common.collect.ImmutableMap; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.output.NullOutputStream; +import org.gradle.api.Project; +import org.gradle.api.logging.LogLevel; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.LoomGradlePlugin; +import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; +import net.fabricmc.loom.configuration.DependencyProvider; +import net.fabricmc.loom.configuration.providers.mappings.GradleMappingContext; +import net.fabricmc.loom.configuration.providers.mappings.mojmap.MojangMappingLayer; +import net.fabricmc.loom.configuration.providers.mappings.mojmap.MojangMappingsSpec; +import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.srg.Tsrg2Utils; +import net.fabricmc.loom.util.srg.Tsrg2Writer; +import net.fabricmc.mappingio.MappingReader; +import net.fabricmc.mappingio.MappingVisitor; +import net.fabricmc.mappingio.adapter.ForwardingMappingVisitor; +import net.fabricmc.mappingio.tree.MappingTree; +import net.fabricmc.mappingio.tree.MemoryMappingTree; + +public class SrgProvider extends DependencyProvider { + private Path srg; + private Boolean isTsrgV2; + private Path mergedMojangRaw; + private Path mergedMojang; + private Path mergedMojangTrimmed; + private static Path mojmapTsrg; + private static Path mojmapTsrg2; + + public SrgProvider(Project project) { + super(project); + } + + @Override + public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception { + init(dependency.getDependency().getVersion()); + + if (!Files.exists(srg) || isRefreshDeps()) { + Path srgZip = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve srg")).toPath(); + + try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + srgZip.toUri()), ImmutableMap.of("create", false))) { + Files.copy(fs.getPath("config", "joined.tsrg"), srg, StandardCopyOption.REPLACE_EXISTING); + } + } + + try (BufferedReader reader = Files.newBufferedReader(srg)) { + isTsrgV2 = reader.readLine().startsWith("tsrg2"); + } + + if (isTsrgV2) { + if (!Files.exists(mergedMojangRaw) || !Files.exists(mergedMojang) || !Files.exists(mergedMojangTrimmed) || isRefreshDeps()) { + Stopwatch stopwatch = Stopwatch.createStarted(); + getProject().getLogger().lifecycle(":merging mappings (InstallerTools, srg + mojmap)"); + PrintStream out = System.out; + PrintStream err = System.err; + + if (getProject().getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) >= 0) { + System.setOut(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM)); + System.setErr(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM)); + } + + Files.deleteIfExists(mergedMojangRaw); + Files.deleteIfExists(mergedMojang); + net.minecraftforge.installertools.ConsoleTool.main(new String[] { + "--task", + "MERGE_MAPPING", + "--left", + getSrg().toAbsolutePath().toString(), + "--right", + getMojmapTsrg2(getProject(), getExtension()).toAbsolutePath().toString(), + "--classes", + "--output", + mergedMojangRaw.toAbsolutePath().toString() + }); + + MemoryMappingTree tree = new MemoryMappingTree(); + MappingReader.read(new StringReader(FileUtils.readFileToString(mergedMojangRaw.toFile(), StandardCharsets.UTF_8)), new FieldDescWrappingVisitor(tree)); + Files.writeString(mergedMojang, Tsrg2Writer.serialize(tree), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + + for (MappingTree.ClassMapping classDef : tree.getClasses()) { + for (MappingTree.MethodMapping methodDef : classDef.getMethods()) { + methodDef.getArgs().clear(); + } + } + + Files.writeString(mergedMojangTrimmed, Tsrg2Writer.serialize(tree), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + + if (getProject().getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) >= 0) { + System.setOut(out); + System.setErr(err); + } + + getProject().getLogger().lifecycle(":merged mappings (InstallerTools, srg + mojmap) in " + stopwatch.stop()); + } + } + } + + // Read mojmap and apply field descs to the tsrg2 + private class FieldDescWrappingVisitor extends ForwardingMappingVisitor { + private final Map<FieldKey, String> fieldDescMap = new HashMap<>(); + private String lastClass; + + protected FieldDescWrappingVisitor(MappingVisitor next) throws IOException { + super(next); + MemoryMappingTree mojmap = new MemoryMappingTree(); + MappingReader.read(getMojmapTsrg2(getProject(), getExtension()), mojmap); + + for (MappingTree.ClassMapping classMapping : mojmap.getClasses()) { + for (MappingTree.FieldMapping fieldMapping : classMapping.getFields()) { + fieldDescMap.put(new FieldKey(classMapping.getSrcName(), fieldMapping.getSrcName()), fieldMapping.getSrcDesc()); + } + } + } + + @Override + public boolean visitClass(String srcName) throws IOException { + if (super.visitClass(srcName)) { + this.lastClass = srcName; + return true; + } else { + return false; + } + } + + @Override + public boolean visitField(String srcName, String srcDesc) throws IOException { + if (srcDesc == null) { + srcDesc = fieldDescMap.get(new FieldKey(lastClass, srcName)); + } + + return super.visitField(srcName, srcDesc); + } + + private record FieldKey(String owner, String name) { + } + } + + private void init(String version) { + File dir = getMinecraftProvider().dir("srg/" + version); + srg = new File(dir, "srg.tsrg").toPath(); + mergedMojangRaw = new File(dir, "srg-mojmap-merged-raw.tsrg").toPath(); + mergedMojang = new File(dir, "srg-mojmap-merged.tsrg").toPath(); + mergedMojangTrimmed = new File(dir, "srg-mojmap-merged-trimmed.tsrg").toPath(); + } + + public Path getSrg() { + return srg; + } + + public Path getMergedMojangRaw() { + if (!isTsrgV2()) throw new IllegalStateException("May not access merged mojmap srg if not on modern Minecraft!"); + + return mergedMojangRaw; + } + + public Path getMergedMojang() { + if (!isTsrgV2()) throw new IllegalStateException("May not access merged mojmap srg if not on modern Minecraft!"); + + return mergedMojang; + } + + public Path getMergedMojangTrimmed() { + if (!isTsrgV2()) throw new IllegalStateException("May not access merged mojmap srg if not on modern Minecraft!"); + + return mergedMojangTrimmed; + } + + public boolean isTsrgV2() { + return isTsrgV2; + } + + public static Path getMojmapTsrg(Project project, LoomGradleExtension extension) throws IOException { + if (mojmapTsrg != null) return mojmapTsrg; + + mojmapTsrg = extension.getMinecraftProvider().dir("forge").toPath().resolve("mojmap.tsrg"); + + if (Files.notExists(mojmapTsrg) || LoomGradlePlugin.refreshDeps) { + try (BufferedWriter writer = Files.newBufferedWriter(mojmapTsrg, StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) { + Tsrg2Utils.writeTsrg(visitor -> visitMojmap(visitor, project), + MappingsNamespace.NAMED.toString(), false, writer); + } + } + + return mojmapTsrg; + } + + public static Path getMojmapTsrg2(Project project, LoomGradleExtension extension) throws IOException { + if (mojmapTsrg2 != null) return mojmapTsrg2; + + mojmapTsrg2 = extension.getMinecraftProvider().dir("forge").toPath().resolve("mojmap.tsrg2"); + + if (Files.notExists(mojmapTsrg2) || LoomGradlePlugin.refreshDeps) { + try (BufferedWriter writer = Files.newBufferedWriter(mojmapTsrg2, StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) { + MemoryMappingTree tree = new MemoryMappingTree(); + visitMojmap(tree, project); + writer.write(Tsrg2Writer.serialize(tree)); + } + } + + return mojmapTsrg2; + } + + private static void visitMojmap(MappingVisitor visitor, Project project) { + GradleMappingContext context = new GradleMappingContext(project, "tmp-mojmap"); + + try { + FileUtils.deleteDirectory(context.workingDirectory("/").toFile()); + MojangMappingLayer layer = new MojangMappingsSpec(() -> true, true).createLayer(context); + layer.visit(visitor); + } catch (IOException e) { + throw new UncheckedIOException(e); + } finally { + try { + FileUtils.deleteDirectory(context.workingDirectory("/").toFile()); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + @Override + public String getTargetConfig() { + return Constants.Configurations.SRG; + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingSpecBuilderImpl.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingSpecBuilderImpl.java index b3746258..a2ffaa3c 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingSpecBuilderImpl.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingSpecBuilderImpl.java @@ -29,12 +29,15 @@ import java.util.LinkedList; import java.util.List; import org.gradle.api.Action; +import org.jetbrains.annotations.Nullable; import net.fabricmc.loom.api.mappings.layered.spec.FileSpec; import net.fabricmc.loom.api.mappings.layered.spec.LayeredMappingSpecBuilder; import net.fabricmc.loom.api.mappings.layered.spec.MappingsSpec; import net.fabricmc.loom.api.mappings.layered.spec.MojangMappingsSpecBuilder; import net.fabricmc.loom.api.mappings.layered.spec.ParchmentMappingsSpecBuilder; +import net.fabricmc.loom.api.LoomGradleExtensionAPI; +import net.fabricmc.loom.configuration.providers.mappings.crane.CraneMappingsSpec; import net.fabricmc.loom.configuration.providers.mappings.extras.signatures.SignatureFixesSpec; import net.fabricmc.loom.configuration.providers.mappings.intermediary.IntermediaryMappingsSpec; import net.fabricmc.loom.configuration.providers.mappings.mojmap.MojangMappingsSpecBuilderImpl; @@ -42,6 +45,12 @@ import net.fabricmc.loom.configuration.providers.mappings.parchment.ParchmentMap public class LayeredMappingSpecBuilderImpl implements LayeredMappingSpecBuilder { private final List<MappingsSpec<?>> layers = new LinkedList<>(); + @Nullable + private final LoomGradleExtensionAPI extension; + + public LayeredMappingSpecBuilderImpl(@Nullable LoomGradleExtensionAPI extension) { + this.extension = extension; + } @Override public LayeredMappingSpecBuilder addLayer(MappingsSpec<?> mappingSpec) { @@ -53,7 +62,8 @@ public class LayeredMappingSpecBuilderImpl implements LayeredMappingSpecBuilder public LayeredMappingSpecBuilder officialMojangMappings(Action<MojangMappingsSpecBuilder> action) { MojangMappingsSpecBuilderImpl builder = MojangMappingsSpecBuilderImpl.builder(); action.execute(builder); - return addLayer(builder.build()); + layers.add(builder.build(() -> extension != null && extension.isSilentMojangMappingsLicenseEnabled())); + return this; } @Override @@ -64,6 +74,12 @@ public class LayeredMappingSpecBuilderImpl implements LayeredMappingSpecBuilder } @Override + public LayeredMappingSpecBuilder crane(Object object) { + layers.add(new CraneMappingsSpec(FileSpec.create(object))); + return this; + } + + @Override public LayeredMappingSpecBuilder signatureFix(Object object) { return addLayer(new SignatureFixesSpec(FileSpec.create(object))); } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingsDependency.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingsDependency.java index dcfae66b..6a47ed71 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingsDependency.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingsDependency.java @@ -37,10 +37,21 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import org.gradle.api.Action; import org.gradle.api.Project; import org.gradle.api.artifacts.Dependency; +import org.gradle.api.artifacts.ExternalDependency; +import org.gradle.api.artifacts.ExternalModuleDependency; +import org.gradle.api.artifacts.ModuleIdentifier; +import org.gradle.api.artifacts.ModuleVersionIdentifier; +import org.gradle.api.artifacts.MutableVersionConstraint; import org.gradle.api.artifacts.FileCollectionDependency; import org.gradle.api.artifacts.SelfResolvingDependency; +import org.gradle.api.artifacts.VersionConstraint; +import org.gradle.api.internal.artifacts.DefaultModuleIdentifier; +import org.gradle.api.internal.artifacts.ModuleVersionSelectorStrictSpec; +import org.gradle.api.internal.artifacts.dependencies.AbstractModuleDependency; +import org.gradle.api.internal.artifacts.dependencies.DefaultMutableVersionConstraint; import org.gradle.api.file.FileCollection; import org.gradle.api.tasks.TaskDependency; @@ -54,7 +65,7 @@ import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch; import net.fabricmc.mappingio.format.Tiny2Writer; import net.fabricmc.mappingio.tree.MemoryMappingTree; -public class LayeredMappingsDependency implements SelfResolvingDependency, FileCollectionDependency { +public class LayeredMappingsDependency extends AbstractModuleDependency implements SelfResolvingDependency, ExternalModuleDependency, FileCollectionDependency { private static final String GROUP = "loom"; private static final String MODULE = "mappings"; @@ -64,6 +75,7 @@ public class LayeredMappingsDependency implements SelfResolvingDependency, FileC private final String version; public LayeredMappingsDependency(Project project, MappingContext mappingContext, LayeredMappingSpec layeredMappingSpec, String version) { + super(null); this.project = project; this.mappingContext = mappingContext; this.layeredMappingSpec = layeredMappingSpec; @@ -145,6 +157,21 @@ public class LayeredMappingsDependency implements SelfResolvingDependency, FileC } @Override + public VersionConstraint getVersionConstraint() { + return new DefaultMutableVersionConstraint(getVersion()); + } + + @Override + public boolean matchesStrictly(ModuleVersionIdentifier identifier) { + return new ModuleVersionSelectorStrictSpec(this).isSatisfiedBy(identifier); + } + + @Override + public ModuleIdentifier getModule() { + return DefaultModuleIdentifier.newId(GROUP, MODULE); + } + + @Override public boolean contentEquals(Dependency dependency) { if (dependency instanceof LayeredMappingsDependency layeredMappingsDependency) { return Objects.equals(layeredMappingsDependency.getVersion(), this.getVersion()); @@ -154,11 +181,35 @@ public class LayeredMappingsDependency implements SelfResolvingDependency, FileC } @Override - public Dependency copy() { + public boolean isChanging() { + return false; + } + + @Override + public ExternalModuleDependency setChanging(boolean b) { + return this; + } + + @Override + public boolean isForce() { + return false; + } + + @Override + public ExternalDependency setForce(boolean b) { + return this; + } + + @Override + public ExternalModuleDependency copy() { return new LayeredMappingsDependency(project, mappingContext, layeredMappingSpec, version); } @Override + public void version(Action<? super MutableVersionConstraint> action) { + } + + @Override public String getReason() { return null; } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java index 8ed07b90..23bf71c8 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java @@ -28,15 +28,19 @@ import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.Reader; +import java.io.UncheckedIOException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.function.Consumer; @@ -47,6 +51,7 @@ import com.google.common.net.UrlEscapers; import com.google.gson.JsonObject; import org.apache.tools.ant.util.StringUtils; import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Opcodes; @@ -59,11 +64,16 @@ import net.fabricmc.loom.configuration.accesswidener.TransitiveAccessWidenerJarP import net.fabricmc.loom.configuration.processors.JarProcessorManager; import net.fabricmc.loom.configuration.processors.MinecraftProcessedProvider; import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl; +import net.fabricmc.loom.configuration.providers.forge.MinecraftPatchedProvider; +import net.fabricmc.loom.configuration.providers.forge.SrgProvider; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.DeletingFileVisitor; import net.fabricmc.loom.util.DownloadUtil; import net.fabricmc.loom.util.ZipUtils; +import net.fabricmc.loom.util.srg.MCPReader; +import net.fabricmc.loom.util.srg.SrgMerger; +import net.fabricmc.loom.util.srg.SrgNamedWriter; import net.fabricmc.mappingio.MappingReader; import net.fabricmc.mappingio.adapter.MappingNsCompleter; import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch; @@ -74,9 +84,12 @@ import net.fabricmc.mappingio.tree.MappingTree; import net.fabricmc.mappingio.tree.MemoryMappingTree; import net.fabricmc.stitch.Command; import net.fabricmc.stitch.commands.CommandProposeFieldNames; +import net.fabricmc.stitch.commands.tinyv2.TinyFile; +import net.fabricmc.stitch.commands.tinyv2.TinyV2Writer; public class MappingsProviderImpl extends DependencyProvider implements MappingsProvider { public MinecraftMappedProvider mappedProvider; + public MinecraftPatchedProvider patchedProvider; public String mappingsIdentifier; @@ -88,10 +101,14 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings // The mappings we use in practice public Path tinyMappings; public Path tinyMappingsJar; + public Path tinyMappingsWithSrg; + public Path mixinTinyMappingsWithSrg; // FORGE: The mixin mappings have srg names in intermediary. + public Path srgToNamedSrg; // FORGE: srg to named in srg file format private Path unpickDefinitions; private boolean hasUnpickDefinitions; private UnpickMetadata unpickMetadata; private MemoryMappingTree mappingTree; + private MemoryMappingTree mappingTreeWithSrg; private Map<String, String> signatureFixes; public MappingsProviderImpl(Project project) { @@ -102,6 +119,10 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings return Objects.requireNonNull(mappingTree, "Cannot get mappings before they have been read"); } + public MemoryMappingTree getMappingsWithSrg() throws IOException { + return Objects.requireNonNull(mappingTreeWithSrg, "Cannot get mappings before they have been read"); + } + @Override public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception { MinecraftProviderImpl minecraftProvider = getDependencyManager().getProvider(MinecraftProviderImpl.class); @@ -118,14 +139,29 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings initFiles(); if (Files.notExists(tinyMappings) || isRefreshDeps()) { - storeMappings(getProject(), minecraftProvider, mappingsJar.toPath()); + storeMappings(getProject(), minecraftProvider, mappingsJar.toPath(), postPopulationScheduler); } else { try (FileSystem fileSystem = FileSystems.newFileSystem(mappingsJar.toPath(), (ClassLoader) null)) { extractExtras(fileSystem); } } - mappingTree = readMappings(); + if (getExtension().isForge()) { + patchedProvider = new MinecraftPatchedProvider(getProject()); + patchedProvider.provide(dependency, postPopulationScheduler); + } + + mappingTree = readMappings(tinyMappings); + manipulateMappings(mappingsJar.toPath()); + + if (getExtension().shouldGenerateSrgTiny()) { + if (Files.notExists(tinyMappingsWithSrg) || isRefreshDeps()) { + // Merge tiny mappings with srg + SrgMerger.mergeSrg(getProject().getLogger(), getExtension().getMappingsProvider()::getMojmapSrgFileIfPossible, getRawSrgFile(), tinyMappings, tinyMappingsWithSrg, true); + } + + mappingTreeWithSrg = readMappings(tinyMappingsWithSrg); + } if (Files.notExists(tinyMappingsJar) || isRefreshDeps()) { Files.deleteIfExists(tinyMappingsJar); @@ -143,7 +179,24 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings populateUnpickClasspath(); } - addDependency(tinyMappingsJar.toFile(), Constants.Configurations.MAPPINGS_FINAL); + if (getExtension().isForge()) { + if (!getExtension().shouldGenerateSrgTiny()) { + throw new IllegalStateException("We have to generate srg tiny in a forge environment!"); + } + + if (Files.notExists(mixinTinyMappingsWithSrg) || isRefreshDeps()) { + List<String> lines = new ArrayList<>(Files.readAllLines(tinyMappingsWithSrg)); + lines.set(0, lines.get(0).replace("intermediary", "yraidemretni").replace("srg", "intermediary")); + Files.deleteIfExists(mixinTinyMappingsWithSrg); + Files.write(mixinTinyMappingsWithSrg, lines); + } + + if (Files.notExists(srgToNamedSrg) || isRefreshDeps()) { + SrgNamedWriter.writeTo(getProject().getLogger(), srgToNamedSrg, getMappingsWithSrg(), "srg", "named"); + } + } + + addDependency(getProject().getDependencies().module("loom.resolved:mappings:" + getMinecraftProvider().minecraftVersion() + "/" + getExtension().getMappingsProvider().mappingsIdentifier()), Constants.Configurations.MAPPINGS_FINAL); LoomGradleExtension extension = getExtension(); @@ -165,7 +218,11 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings extension.setJarProcessorManager(processorManager); processorManager.setupProcessors(); - if (processorManager.active()) { + if (extension.isForge()) { + patchedProvider.finishProvide(); + } + + if (processorManager.active() || (extension.isForge() && patchedProvider.usesProjectCache())) { mappedProvider = new MinecraftProcessedProvider(getProject(), processorManager); getProject().getLogger().info("Using project based jar storage"); } else { @@ -176,6 +233,28 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings mappedProvider.provide(dependency, postPopulationScheduler); } + protected Path getRawSrgFile() throws IOException { + LoomGradleExtension extension = getExtension(); + + if (extension.getSrgProvider().isTsrgV2()) { + return extension.getSrgProvider().getMergedMojangTrimmed(); + } + + return extension.getSrgProvider().getSrg(); + } + + public Path getMojmapSrgFileIfPossible() { + try { + LoomGradleExtension extension = getExtension(); + return SrgProvider.getMojmapTsrg2(getProject(), extension); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void manipulateMappings(Path mappingsJar) throws IOException { + } + private String getMappingsClassifier(DependencyInfo dependency, boolean isV2) { String[] depStringSplit = dependency.getDepString().split(":"); @@ -200,21 +279,37 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings } // We can save reading the zip file + header by checking the file name - return mappingsJar.getName().endsWith("-v2.jar"); + return mappingsJar.getName().endsWith("-v2.jar") || mappingsJar.getName().endsWith("-mergedv2.jar"); } else { return doesJarContainV2Mappings(mappingsJar.toPath()); } } - private void storeMappings(Project project, MinecraftProviderImpl minecraftProvider, Path yarnJar) throws IOException { + private void storeMappings(Project project, MinecraftProviderImpl minecraftProvider, Path yarnJar, Consumer<Runnable> postPopulationScheduler) + throws IOException { project.getLogger().info(":extracting " + yarnJar.getFileName()); + if (isMCP(yarnJar)) { + try { + readAndMergeMCP(yarnJar, postPopulationScheduler); + } catch (Exception e) { + throw new RuntimeException(e); + } + + return; + } + try (FileSystem fileSystem = FileSystems.newFileSystem(yarnJar, (ClassLoader) null)) { extractMappings(fileSystem, baseTinyMappings); extractExtras(fileSystem); } - if (areMappingsV2(baseTinyMappings)) { + if (areMappingsMergedV2(baseTinyMappings)) { + // Architectury Loom Patch + // If a merged tiny v2 mappings file is provided + // Skip merging, should save a lot of time + Files.copy(baseTinyMappings, tinyMappings, StandardCopyOption.REPLACE_EXISTING); + } else if (areMappingsV2(baseTinyMappings)) { // These are unmerged v2 mappings mergeAndSaveMappings(project, baseTinyMappings, tinyMappings); } else { @@ -225,15 +320,53 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings } } - private MemoryMappingTree readMappings() throws IOException { + private static MemoryMappingTree readMappings(Path file) throws IOException { MemoryMappingTree mappingTree = new MemoryMappingTree(); - MappingReader.read(tinyMappings, mappingTree); + MappingReader.read(file, mappingTree); return mappingTree; } + private void readAndMergeMCP(Path mcpJar, Consumer<Runnable> postPopulationScheduler) throws Exception { + Path intermediaryTinyPath = getIntermediaryTiny(); + SrgProvider provider = getExtension().getSrgProvider(); + + if (provider == null) { + if (!getExtension().shouldGenerateSrgTiny()) { + Configuration srg = getProject().getConfigurations().maybeCreate(Constants.Configurations.SRG); + srg.setTransitive(false); + } + + provider = new SrgProvider(getProject()); + getProject().getDependencies().add(provider.getTargetConfig(), "de.oceanlabs.mcp:mcp_config:" + getMinecraftProvider().minecraftVersion()); + Configuration configuration = getProject().getConfigurations().getByName(provider.getTargetConfig()); + provider.provide(DependencyInfo.create(getProject(), configuration.getDependencies().iterator().next(), configuration), postPopulationScheduler); + } + + Path srgPath = getRawSrgFile(); + TinyFile file = new MCPReader(intermediaryTinyPath, srgPath).read(mcpJar); + TinyV2Writer.write(file, tinyMappings); + } + + private boolean isMCP(Path path) throws IOException { + try (FileSystem fs = FileSystems.newFileSystem(path, (ClassLoader) null)) { + return Files.exists(fs.getPath("fields.csv")) && Files.exists(fs.getPath("methods.csv")); + } + } + private static boolean areMappingsV2(Path path) throws IOException { try (BufferedReader reader = Files.newBufferedReader(path)) { return MappingReader.detectFormat(reader) == MappingFormat.TINY_2; + } catch (NoSuchFileException e) { + // TODO: just check the mappings version when Parser supports V1 in readMetadata() + return false; + } + } + + private static boolean areMappingsMergedV2(Path path) throws IOException { + try (BufferedReader reader = Files.newBufferedReader(path)) { + return MappingReader.detectFormat(reader) == MappingFormat.TINY_2 && MappingReader.getNamespaces(reader).containsAll(Arrays.asList("named", "intermediary", "official")); + } catch (NoSuchFileException e) { + return false; } } @@ -242,6 +375,8 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings try (BufferedReader reader = Files.newBufferedReader(fs.getPath("mappings", "mappings.tiny"))) { return MappingReader.detectFormat(reader) == MappingFormat.TINY_2; } + } catch (NoSuchFileException e) { + return false; } } @@ -325,22 +460,18 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings Stopwatch stopwatch = Stopwatch.createStarted(); project.getLogger().info(":merging mappings"); - MemoryMappingTree intermediaryTree = new MemoryMappingTree(); - readIntermediaryTree().accept(new MappingSourceNsSwitch(intermediaryTree, MappingsNamespace.INTERMEDIARY.toString())); + MemoryMappingTree tree = new MemoryMappingTree(); + MappingSourceNsSwitch sourceNsSwitch = new MappingSourceNsSwitch(tree, MappingsNamespace.OFFICIAL.toString()); + readIntermediaryTree().accept(sourceNsSwitch); try (BufferedReader reader = Files.newBufferedReader(from, StandardCharsets.UTF_8)) { - Tiny2Reader.read(reader, intermediaryTree); + Tiny2Reader.read(reader, tree); } - MemoryMappingTree officialTree = new MemoryMappingTree(); - MappingNsCompleter nsCompleter = new MappingNsCompleter(officialTree, Map.of(MappingsNamespace.OFFICIAL.toString(), MappingsNamespace.INTERMEDIARY.toString())); - MappingSourceNsSwitch nsSwitch = new MappingSourceNsSwitch(nsCompleter, MappingsNamespace.OFFICIAL.toString()); - intermediaryTree.accept(nsSwitch); - - inheritMappedNamesOfEnclosingClasses(officialTree); + inheritMappedNamesOfEnclosingClasses(tree); try (Tiny2Writer writer = new Tiny2Writer(Files.newBufferedWriter(out, StandardCharsets.UTF_8), false)) { - officialTree.accept(writer); + tree.accept(writer); } project.getLogger().info(":merged mappings in " + stopwatch.stop()); @@ -394,8 +525,8 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings private void suggestFieldNames(MinecraftProviderImpl minecraftProvider, Path oldMappings, Path newMappings) { Command command = new CommandProposeFieldNames(); runCommand(command, minecraftProvider.getMergedJar().getAbsolutePath(), - oldMappings.toAbsolutePath().toString(), - newMappings.toAbsolutePath().toString()); + oldMappings.toAbsolutePath().toString(), + newMappings.toAbsolutePath().toString()); } private void runCommand(Command command, String... args) { @@ -412,6 +543,9 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings tinyMappings = mappingsWorkingDir.resolve("mappings.tiny"); tinyMappingsJar = mappingsWorkingDir.resolve("mappings.jar"); unpickDefinitions = mappingsWorkingDir.resolve("mappings.unpick"); + tinyMappingsWithSrg = mappingsWorkingDir.resolve("mappings-srg.tiny"); + mixinTinyMappingsWithSrg = mappingsWorkingDir.resolve("mappings-mixin-srg.tiny"); + srgToNamedSrg = mappingsWorkingDir.resolve("mappings-srg-named.srg"); if (isRefreshDeps()) { cleanFiles(); @@ -459,7 +593,7 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings return mappingsWorkingDir; } - private String createMappingsIdentifier(String mappingsName, String version, String classifier) { + protected String createMappingsIdentifier(String mappingsName, String version, String classifier) { // mappingsName . mcVersion . version classifier // Example: net.fabricmc.yarn . 1_16_5 . 1.16.5+build.5 -v2 return mappingsName + "." + getMinecraftProvider().minecraftVersion().replace(' ', '_').replace('.', '_').replace('-', '_') + "." + version + classifier; diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/crane/CraneMappingLayer.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/crane/CraneMappingLayer.java new file mode 100644 index 00000000..0d1b6c3f --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/crane/CraneMappingLayer.java @@ -0,0 +1,49 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.providers.mappings.crane; + +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +import net.fabricmc.loom.api.mappings.layered.MappingLayer; +import net.fabricmc.loom.util.FileSystemUtil; +import net.fabricmc.mappingio.MappingVisitor; +import net.fabricmc.mappingio.format.Tiny2Reader; + +public record CraneMappingLayer(Path craneJar) implements MappingLayer { + private static final String TINY_FILE_NAME = "crane.tiny"; + + @Override + public void visit(MappingVisitor visitor) throws IOException { + try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(craneJar(), false)) { + try (BufferedReader reader = Files.newBufferedReader(fs.get().getPath(TINY_FILE_NAME), StandardCharsets.UTF_8)) { + Tiny2Reader.read(reader, visitor); + } + } + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/crane/CraneMappingsSpec.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/crane/CraneMappingsSpec.java new file mode 100644 index 00000000..04c98170 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/crane/CraneMappingsSpec.java @@ -0,0 +1,36 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.providers.mappings.crane; + +import net.fabricmc.loom.api.mappings.layered.MappingContext; +import net.fabricmc.loom.api.mappings.layered.spec.FileSpec; +import net.fabricmc.loom.api.mappings.layered.spec.MappingsSpec; + +public record CraneMappingsSpec(FileSpec fileSpec) implements MappingsSpec<CraneMappingLayer> { + @Override + public CraneMappingLayer createLayer(MappingContext context) { + return new CraneMappingLayer(fileSpec().get(context)); + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/intermediary/IntermediaryMappingLayer.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/intermediary/IntermediaryMappingLayer.java index a6c06ba9..83b3c5da 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/intermediary/IntermediaryMappingLayer.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/intermediary/IntermediaryMappingLayer.java @@ -30,6 +30,7 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.Collections; +import java.util.function.Supplier; import net.fabricmc.loom.api.mappings.layered.MappingLayer; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; @@ -37,7 +38,7 @@ import net.fabricmc.mappingio.MappingVisitor; import net.fabricmc.mappingio.adapter.MappingNsCompleter; import net.fabricmc.mappingio.format.Tiny2Reader; -public record IntermediaryMappingLayer(File tinyFile) implements MappingLayer { +public record IntermediaryMappingLayer(Supplier<File> tinyFile) implements MappingLayer { @Override public MappingsNamespace getSourceNamespace() { return MappingsNamespace.OFFICIAL; @@ -48,7 +49,7 @@ public record IntermediaryMappingLayer(File tinyFile) implements MappingLayer { // Populate named with intermediary and add Add a "named" namespace MappingNsCompleter nsCompleter = new MappingNsCompleter(mappingVisitor, Collections.singletonMap(MappingsNamespace.NAMED.toString(), MappingsNamespace.INTERMEDIARY.toString()), true); - try (BufferedReader reader = Files.newBufferedReader(tinyFile().toPath(), StandardCharsets.UTF_8)) { + try (BufferedReader reader = Files.newBufferedReader(tinyFile().get().toPath(), StandardCharsets.UTF_8)) { Tiny2Reader.read(reader, nsCompleter); } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/intermediary/IntermediaryMappingsSpec.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/intermediary/IntermediaryMappingsSpec.java index 194b3038..95277f52 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/intermediary/IntermediaryMappingsSpec.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/intermediary/IntermediaryMappingsSpec.java @@ -30,6 +30,6 @@ import net.fabricmc.loom.api.mappings.layered.spec.MappingsSpec; public record IntermediaryMappingsSpec() implements MappingsSpec<IntermediaryMappingLayer> { @Override public IntermediaryMappingLayer createLayer(MappingContext context) { - return new IntermediaryMappingLayer(context.mappingsProvider().intermediaryTinyFile()); + return new IntermediaryMappingLayer(context.mappingsProvider()::intermediaryTinyFile); } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingLayer.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingLayer.java index 541fd489..5d970e5e 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingLayer.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingLayer.java @@ -45,20 +45,23 @@ import net.fabricmc.mappingio.MappingVisitor; import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch; import net.fabricmc.mappingio.format.ProGuardReader; -public record MojangMappingLayer(MinecraftVersionMeta.Download clientDownload, +public record MojangMappingLayer(String minecraftVersion, + MinecraftVersionMeta.Download clientDownload, MinecraftVersionMeta.Download serverDownload, Path workingDir, boolean nameSyntheticMembers, - Logger logger) implements MappingLayer { + Logger logger, + MojangMappingsSpec.SilenceLicenseOption silenceLicense) implements MappingLayer { private static final Pattern SYNTHETIC_NAME_PATTERN = Pattern.compile("^(access|this|val\\$this|lambda\\$.*)\\$[0-9]+$"); - @Override public void visit(MappingVisitor mappingVisitor) throws IOException { - Path clientMappings = workingDir().resolve("client.txt"); - Path serverMappings = workingDir().resolve("server.txt"); + Path clientMappings = workingDir().resolve("%s.client.txt".formatted(minecraftVersion)); + Path serverMappings = workingDir().resolve("%s.server.txt".formatted(minecraftVersion)); download(clientMappings, serverMappings); - printMappingsLicense(clientMappings); + if (!silenceLicense.isSilent()) { + printMappingsLicense(clientMappings); + } // Filter out field names matching the pattern DstNameFilterMappingVisitor nameFilter = new DstNameFilterMappingVisitor(mappingVisitor, SYNTHETIC_NAME_PATTERN); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingsSpec.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingsSpec.java index f77fb57d..43b33c24 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingsSpec.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingsSpec.java @@ -28,11 +28,47 @@ import net.fabricmc.loom.api.mappings.layered.MappingContext; import net.fabricmc.loom.api.mappings.layered.spec.MappingsSpec; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta; -public record MojangMappingsSpec(boolean nameSyntheticMembers) implements MappingsSpec<MojangMappingLayer> { +public record MojangMappingsSpec(SilenceLicenseOption silenceLicense, boolean nameSyntheticMembers) implements MappingsSpec<MojangMappingLayer> { // Keys in dependency manifest private static final String MANIFEST_CLIENT_MAPPINGS = "client_mappings"; private static final String MANIFEST_SERVER_MAPPINGS = "server_mappings"; + public MojangMappingsSpec(SilenceLicenseSupplier supplier, boolean nameSyntheticMembers) { + this(new SilenceLicenseOption(supplier), nameSyntheticMembers); + } + + public MojangMappingsSpec(boolean nameSyntheticMembers) { + this(() -> false, nameSyntheticMembers); + } + + @FunctionalInterface + public interface SilenceLicenseSupplier { + boolean isSilent(); + } + + public record SilenceLicenseOption(SilenceLicenseSupplier supplier) { + public boolean isSilent() { + return supplier.isSilent(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SilenceLicenseOption that)) return false; + return isSilent() == that.isSilent(); + } + + @Override + public int hashCode() { + return Boolean.hashCode(isSilent()); + } + + @Override + public String toString() { + return isSilent() + ""; + } + } + @Override public MojangMappingLayer createLayer(MappingContext context) { MinecraftVersionMeta versionInfo = context.minecraftProvider().getVersionInfo(); @@ -42,11 +78,13 @@ public record MojangMappingsSpec(boolean nameSyntheticMembers) implements Mappin } return new MojangMappingLayer( + context.minecraftVersion(), versionInfo.download(MANIFEST_CLIENT_MAPPINGS), versionInfo.download(MANIFEST_SERVER_MAPPINGS), context.workingDirectory("mojang"), nameSyntheticMembers(), - context.getLogger() + context.getLogger(), + silenceLicense() ); } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingsSpecBuilderImpl.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingsSpecBuilderImpl.java index 7937ad1f..fd7c15ad 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingsSpecBuilderImpl.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingsSpecBuilderImpl.java @@ -48,7 +48,7 @@ public class MojangMappingsSpecBuilderImpl implements MojangMappingsSpecBuilder return nameSyntheticMembers; } - public MojangMappingsSpec build() { - return new MojangMappingsSpec(nameSyntheticMembers); + public MojangMappingsSpec build(MojangMappingsSpec.SilenceLicenseSupplier supplier) { + return new MojangMappingsSpec(supplier, nameSyntheticMembers); } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java index 264b855c..ec003995 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java @@ -28,14 +28,28 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; +import com.google.common.base.Stopwatch; +import dev.architectury.tinyremapper.IMappingProvider; +import dev.architectury.tinyremapper.InputTag; +import dev.architectury.tinyremapper.NonClassCopyMode; +import dev.architectury.tinyremapper.OutputConsumerPath; +import dev.architectury.tinyremapper.TinyRemapper; +import dev.architectury.tinyremapper.api.TrClass; +import org.apache.commons.lang3.mutable.Mutable; +import org.apache.commons.lang3.tuple.Triple; import org.gradle.api.Project; +import org.jetbrains.annotations.Nullable; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.commons.Remapper; @@ -43,17 +57,30 @@ import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.configuration.DependencyProvider; import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl; import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; +import net.fabricmc.loom.configuration.providers.minecraft.tr.OutputRemappingHandler; +import net.fabricmc.loom.configuration.sources.ForgeSourcesRemapper; import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.DownloadUtil; +import net.fabricmc.loom.util.FileSystemUtil; +import net.fabricmc.loom.util.OperatingSystem; +import net.fabricmc.loom.util.ThreadingUtils; import net.fabricmc.loom.util.TinyRemapperHelper; -import net.fabricmc.tinyremapper.OutputConsumerPath; -import net.fabricmc.tinyremapper.TinyRemapper; -import net.fabricmc.tinyremapper.api.TrClass; +import net.fabricmc.loom.util.srg.AtRemapper; +import net.fabricmc.loom.util.srg.CoreModClassRemapper; +import net.fabricmc.loom.util.srg.InnerClassRemapper; +import net.fabricmc.mappingio.tree.MemoryMappingTree; public class MinecraftMappedProvider extends DependencyProvider { + private File inputJar; + private File inputForgeJar; private File minecraftMappedJar; private File minecraftIntermediaryJar; + private File minecraftSrgJar; + private File forgeMappedJar; + private File forgeIntermediaryJar; + private File forgeSrgJar; - private MinecraftProviderImpl minecraftProvider; + protected MinecraftProviderImpl minecraftProvider; public MinecraftMappedProvider(Project project) { super(project); @@ -65,11 +92,22 @@ public class MinecraftMappedProvider extends DependencyProvider { throw new RuntimeException("mappings file not found"); } - if (!getExtension().getMinecraftProvider().getMergedJar().exists()) { + if (!inputJar.exists()) { throw new RuntimeException("input merged jar not found"); } - if (!minecraftMappedJar.exists() || !getIntermediaryJar().exists() || isRefreshDeps()) { + boolean isForgeAtDirty = getExtension().isForge() && getExtension().getMappingsProvider().patchedProvider.isAtDirty(); + boolean needToRemap = false; + + if (!minecraftMappedJar.exists() || !getIntermediaryJar().exists() || (getExtension().isForge() && !getSrgJar().exists()) || isRefreshDeps() || isForgeAtDirty) { + needToRemap = true; + } + + if (getExtension().isForgeAndNotOfficial() && (!getForgeMappedJar().exists() || !getForgeIntermediaryJar().exists() || !getForgeSrgJar().exists() || isRefreshDeps() || isForgeAtDirty)) { + needToRemap = true; + } + + if (needToRemap) { if (minecraftMappedJar.exists()) { minecraftMappedJar.delete(); } @@ -80,12 +118,37 @@ public class MinecraftMappedProvider extends DependencyProvider { minecraftIntermediaryJar.delete(); } + if (getExtension().isForge() && minecraftSrgJar.exists()) { + minecraftSrgJar.delete(); + } + + if (getExtension().isForgeAndNotOfficial()) { + if (getForgeMappedJar().exists()) { + getForgeMappedJar().delete(); + } + + getForgeMappedJar().getParentFile().mkdirs(); + getForgeIntermediaryJar().delete(); + getForgeSrgJar().delete(); + } + try { - mapMinecraftJar(); + TinyRemapper[] remapperArray = new TinyRemapper[] {null}; + mapMinecraftJar(remapperArray); + remapperArray[0].finish(); } catch (Throwable t) { // Cleanup some some things that may be in a bad state now - minecraftMappedJar.delete(); - minecraftIntermediaryJar.delete(); + DownloadUtil.delete(minecraftMappedJar); + DownloadUtil.delete(minecraftIntermediaryJar); + getExtension().getMinecraftProvider().deleteFiles(); + + if (getExtension().isForge()) { + DownloadUtil.delete(minecraftSrgJar); + DownloadUtil.delete(forgeMappedJar); + DownloadUtil.delete(forgeSrgJar); + DownloadUtil.delete(forgeIntermediaryJar); + } + getExtension().getMappingsProvider().cleanFiles(); throw new RuntimeException("Failed to remap minecraft", t); } @@ -96,35 +159,139 @@ public class MinecraftMappedProvider extends DependencyProvider { } addDependencies(dependency, postPopulationScheduler); + + if (getExtension().isForgeAndNotOfficial()) { + getProject().getDependencies().add(Constants.Configurations.FORGE_NAMED, + getProject().getDependencies().module("net.minecraftforge-loom:forge-mapped:" + getMinecraftProvider().minecraftVersion() + "/" + getExtension().getMappingsProvider().mappingsIdentifier() + "/forge")); + } + + if (getExtension().isForge()) { + getProject().afterEvaluate(project -> { + if (!OperatingSystem.isCIBuild()) { + try { + ForgeSourcesRemapper.addBaseForgeSources(project, getExtension().isForgeAndOfficial()); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + } } - private void mapMinecraftJar() throws IOException { - String fromM = MappingsNamespace.OFFICIAL.toString(); + private byte[][] inputBytes(Path input) throws IOException { + List<byte[]> inputByteList = new ArrayList<>(); + + try (FileSystemUtil.Delegate inputFs = FileSystemUtil.getJarFileSystem(input, false)) { + ThreadingUtils.TaskCompleter taskCompleter = ThreadingUtils.taskCompleter(); + + for (Path path : (Iterable<? extends Path>) Files.walk(inputFs.get().getPath("/"))::iterator) { + if (Files.isRegularFile(path)) { + if (path.getFileName().toString().endsWith(".class")) { + taskCompleter.add(() -> { + byte[] bytes = Files.readAllBytes(path); + + synchronized (inputByteList) { + inputByteList.add(bytes); + } + }); + } + } + } - MappingsProviderImpl mappingsProvider = getExtension().getMappingsProvider(); + taskCompleter.complete(); + } - Path input = minecraftProvider.getMergedJar().toPath(); + return inputByteList.toArray(new byte[0][0]); + } + + private void assetsOut(Path input, @Nullable Path assetsOut) throws IOException { + if (assetsOut != null) { + try (OutputConsumerPath tmpAssetsPath = new OutputConsumerPath.Builder(assetsOut).assumeArchive(true).build()) { + if (getExtension().isForge()) { + tmpAssetsPath.addNonClassFiles(input, NonClassCopyMode.FIX_META_INF, null); + } else { + tmpAssetsPath.addNonClassFiles(input); + } + } + } + } + + private void mapMinecraftJar(TinyRemapper[] remapperArray) throws Exception { + Path input = inputJar.toPath(); + Path inputForge = inputForgeJar == null ? null : inputForgeJar.toPath(); Path outputMapped = minecraftMappedJar.toPath(); Path outputIntermediary = minecraftIntermediaryJar.toPath(); + Path outputSrg = minecraftSrgJar == null ? null : minecraftSrgJar.toPath(); + + Path forgeOutputMapped = forgeMappedJar == null ? null : forgeMappedJar.toPath(); + Path forgeOutputIntermediary = forgeIntermediaryJar == null ? null : forgeIntermediaryJar.toPath(); + Path forgeOutputSrg = forgeSrgJar == null ? null : forgeSrgJar.toPath(); - for (String toM : Arrays.asList(MappingsNamespace.NAMED.toString(), MappingsNamespace.INTERMEDIARY.toString())) { - final boolean toNamed = MappingsNamespace.NAMED.toString().equals(toM); - final boolean toIntermediary = MappingsNamespace.INTERMEDIARY.toString().equals(toM); - final boolean fixSignatures = mappingsProvider.getSignatureFixes() != null; - final Path output = toNamed ? outputMapped : outputIntermediary; + Path vanillaAssets = Files.createTempFile("assets", null); + Files.deleteIfExists(vanillaAssets); + vanillaAssets.toFile().deleteOnExit(); + Path forgeAssets = Files.createTempFile("assets", null); + Files.deleteIfExists(forgeAssets); + forgeAssets.toFile().deleteOnExit(); + Info vanilla = new Info(vanillaAssets, input, outputMapped, outputIntermediary, outputSrg); + Info forge = getExtension().isForgeAndNotOfficial() ? new Info(forgeAssets, inputForge, forgeOutputMapped, forgeOutputIntermediary, forgeOutputSrg) : null; + + Triple<TinyRemapper, Mutable<MemoryMappingTree>, List<TinyRemapper.ApplyVisitorProvider>> pair = TinyRemapperHelper.getTinyRemapper(getProject(), true, builder -> { }); + TinyRemapper remapper = remapperArray[0] = pair.getLeft(); + + assetsOut(input, vanillaAssets); + + if (getExtension().isForgeAndNotOfficial()) { + assetsOut(inputForge, forgeAssets); + } + + remap(remapper, pair.getMiddle(), pair.getRight(), vanilla, forge, MappingsNamespace.OFFICIAL.toString()); + } + + public static class Info { + Path assets; + Path input; + Path outputMapped; + Path outputIntermediary; + Path outputSrg; + + public Info(Path assets, Path input, Path outputMapped, Path outputIntermediary, Path outputSrg) { + this.assets = assets; + this.input = input; + this.outputMapped = outputMapped; + this.outputIntermediary = outputIntermediary; + this.outputSrg = outputSrg; + } + } + + public void remap(TinyRemapper remapper, Mutable<MemoryMappingTree> mappings, List<TinyRemapper.ApplyVisitorProvider> postApply, Info vanilla, @Nullable Info forge, String fromM) throws IOException { + Set<String> classNames = getExtension().isForge() ? InnerClassRemapper.readClassNames(vanilla.input) : null; + + for (String toM : getExtension().isForge() ? Arrays.asList(MappingsNamespace.INTERMEDIARY.toString(), MappingsNamespace.SRG.toString(), MappingsNamespace.NAMED.toString()) : Arrays.asList(MappingsNamespace.INTERMEDIARY.toString(), MappingsNamespace.NAMED.toString())) { + Path output = MappingsNamespace.NAMED.toString().equals(toM) ? vanilla.outputMapped : MappingsNamespace.SRG.toString().equals(toM) ? vanilla.outputSrg : vanilla.outputIntermediary; + Path outputForge = forge == null ? null : MappingsNamespace.NAMED.toString().equals(toM) ? forge.outputMapped : MappingsNamespace.SRG.toString().equals(toM) ? forge.outputSrg : forge.outputIntermediary; + InputTag vanillaTag = remapper.createInputTag(); + InputTag forgeTag = remapper.createInputTag(); + Stopwatch stopwatch = Stopwatch.createStarted(); + final boolean fixSignatures = getExtension().getMappingsProvider().getSignatureFixes() != null; getProject().getLogger().lifecycle(":remapping minecraft (TinyRemapper, " + fromM + " -> " + toM + ")"); - Files.deleteIfExists(output); + remapper.readInputs(vanillaTag, vanilla.input); + + if (forge != null) { + remapper.readInputs(forgeTag, forge.input); + } + + remapper.replaceMappings(getMappings(classNames, fromM, toM, mappings)); + if (!MappingsNamespace.INTERMEDIARY.toString().equals(toM)) mappings.setValue(null); + postApply.clear(); // Bit ugly but whatever, the whole issue is a bit ugly :) AtomicReference<Map<String, String>> remappedSignatures = new AtomicReference<>(); - TinyRemapper remapper = TinyRemapperHelper.getTinyRemapper(getProject(), fromM, toM, true, (builder) -> { - if (!fixSignatures) { - return; - } - builder.extraPostApplyVisitor(new TinyRemapper.ApplyVisitorProvider() { + if (fixSignatures) { + postApply.add(new TinyRemapper.ApplyVisitorProvider() { @Override public ClassVisitor insertApplyVisitor(TrClass cls, ClassVisitor next) { return new ClassVisitor(Constants.ASM_VERSION, next) { @@ -145,20 +312,18 @@ public class MinecraftMappedProvider extends DependencyProvider { }; } }); - }); - if (fixSignatures) { - if (toIntermediary) { + if (MappingsNamespace.INTERMEDIARY.toString().equals(toM)) { // No need to remap, as these are already intermediary - remappedSignatures.set(mappingsProvider.getSignatureFixes()); + remappedSignatures.set(getExtension().getMappingsProvider().getSignatureFixes()); } else { // Remap the sig fixes from intermediary to the target namespace final Map<String, String> remapped = new HashMap<>(); - final TinyRemapper sigTinyRemapper = TinyRemapperHelper.getTinyRemapper(getProject(), MappingsNamespace.INTERMEDIARY.toString(), toM); + final TinyRemapper sigTinyRemapper = TinyRemapperHelper.getTinyRemapper(getProject(), fromM, toM); final Remapper sigAsmRemapper = sigTinyRemapper.getRemapper(); // Remap the class names and the signatures using a new tiny remapper instance. - for (Map.Entry<String, String> entry : mappingsProvider.getSignatureFixes().entrySet()) { + for (Map.Entry<String, String> entry : getExtension().getMappingsProvider().getSignatureFixes().entrySet()) { remapped.put( sigAsmRemapper.map(entry.getKey()), sigAsmRemapper.mapSignature(entry.getValue(), false) @@ -170,28 +335,65 @@ public class MinecraftMappedProvider extends DependencyProvider { } } - try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(output).build()) { - outputConsumer.addNonClassFiles(input); - remapper.readClassPath(TinyRemapperHelper.getMinecraftDependencies(getProject())); - remapper.readInputs(input); - remapper.apply(outputConsumer); - } catch (Exception e) { - throw new RuntimeException("Failed to remap JAR " + input + " with mappings from " + mappingsProvider.tinyMappings, e); - } finally { - remapper.finish(); + OutputRemappingHandler.remap(remapper, vanilla.assets, output, null, vanillaTag); + + if (forge != null) { + OutputRemappingHandler.remap(remapper, forge.assets, outputForge, null, forgeTag); + } + + getProject().getLogger().lifecycle(":remapped minecraft (TinyRemapper, " + fromM + " -> " + toM + ") in " + stopwatch); + remapper.removeInput(); + mappings.setValue(null); + + if (getExtension().isForge() && !"srg".equals(toM)) { + getProject().getLogger().info(":running minecraft finalising tasks"); + + MemoryMappingTree yarnWithSrg = getExtension().getMappingsProvider().getMappingsWithSrg(); + AtRemapper.remap(getProject().getLogger(), output, yarnWithSrg); + CoreModClassRemapper.remapJar(output, yarnWithSrg, getProject().getLogger()); + } + } + } + + public Set<IMappingProvider> getMappings(@Nullable Set<String> fromClassNames, String fromM, String toM, Mutable<MemoryMappingTree> mappings) throws IOException { + Set<IMappingProvider> providers = new HashSet<>(); + mappings.setValue(getExtension().isForge() ? getExtension().getMappingsProvider().getMappingsWithSrg() : getExtension().getMappingsProvider().getMappings()); + providers.add(TinyRemapperHelper.create(mappings.getValue(), fromM, toM, true)); + + if (getExtension().isForge()) { + if (fromClassNames != null) { + providers.add(InnerClassRemapper.of(fromClassNames, getExtension().getMappingsProvider().getMappingsWithSrg(), fromM, toM)); } + } else { + providers.add(out -> TinyRemapperHelper.JSR_TO_JETBRAINS.forEach(out::acceptClass)); } + + return providers; } protected void addDependencies(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) { getProject().getDependencies().add(Constants.Configurations.MINECRAFT_NAMED, - getProject().getDependencies().module("net.minecraft:minecraft-mapped:" + getMinecraftProvider().minecraftVersion() + "/" + getExtension().getMappingsProvider().mappingsIdentifier())); + getProject().getDependencies().module("net.minecraft:" + minecraftProvider.getJarPrefix() + "minecraft-mapped:" + getMinecraftProvider().minecraftVersion() + "/" + getExtension().getMappingsProvider().mappingsIdentifier())); } public void initFiles(MinecraftProviderImpl minecraftProvider, MappingsProviderImpl mappingsProvider) { this.minecraftProvider = minecraftProvider; minecraftIntermediaryJar = new File(getExtension().getMappingsProvider().mappingsWorkingDir().toFile(), "minecraft-intermediary.jar"); - minecraftMappedJar = new File(getExtension().getMappingsProvider().mappingsWorkingDir().toFile(), "minecraft-mapped.jar"); + minecraftSrgJar = !getExtension().isForge() ? null : new File(getExtension().getMappingsProvider().mappingsWorkingDir().toFile(), "minecraft-srg.jar"); + minecraftMappedJar = new File(getExtension().getMappingsProvider().mappingsWorkingDir().toFile(), minecraftProvider.getJarPrefix() + "minecraft-mapped.jar"); + inputJar = getExtension().isForge() ? mappingsProvider.patchedProvider.getMergedJar() : minecraftProvider.getMergedJar(); + + if (getExtension().isForgeAndNotOfficial()) { + inputForgeJar = mappingsProvider.patchedProvider.getForgeMergedJar(); + forgeIntermediaryJar = new File(getExtension().getMappingsProvider().mappingsWorkingDir().toFile(), "forge/forge-intermediary.jar"); + forgeSrgJar = new File(getExtension().getMappingsProvider().mappingsWorkingDir().toFile(), "forge/forge-srg.jar"); + forgeMappedJar = new File(getExtension().getMappingsProvider().mappingsWorkingDir().toFile(), "forge/forge-mapped.jar"); + } else { + inputForgeJar = null; + forgeIntermediaryJar = null; + forgeSrgJar = null; + forgeMappedJar = null; + } } protected File getJarDirectory(File parentDirectory, String type) { @@ -199,13 +401,17 @@ public class MinecraftMappedProvider extends DependencyProvider { } protected String getJarVersionString(String type) { - return String.format("%s-%s", type, getExtension().getMappingsProvider().mappingsIdentifier()); + return String.format("%s-%s%s", type, getExtension().getMappingsProvider().mappingsIdentifier(), minecraftProvider.getJarPrefix()); } public File getIntermediaryJar() { return minecraftIntermediaryJar; } + public File getSrgJar() { + return minecraftSrgJar; + } + public File getMappedJar() { return minecraftMappedJar; } @@ -214,6 +420,18 @@ public class MinecraftMappedProvider extends DependencyProvider { return minecraftMappedJar; } + public File getForgeIntermediaryJar() { + return forgeIntermediaryJar; + } + + public File getForgeSrgJar() { + return forgeSrgJar; + } + + public File getForgeMappedJar() { + return forgeMappedJar; + } + public File getUnpickedJar() { return new File(getExtension().getMappingsProvider().mappingsWorkingDir().toFile(), "minecraft-unpicked.jar"); } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/assets/MinecraftAssetsProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/assets/MinecraftAssetsProvider.java index 87f41e0d..de49c3ba 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/assets/MinecraftAssetsProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/assets/MinecraftAssetsProvider.java @@ -28,14 +28,16 @@ import java.io.File; import java.io.FileReader; import java.io.IOException; import java.net.URL; -import java.util.Deque; import java.util.Map; -import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import com.google.common.base.Stopwatch; +import me.tongfei.progressbar.DelegatingProgressBarConsumer; +import me.tongfei.progressbar.ProgressBar; +import me.tongfei.progressbar.ProgressBarBuilder; +import me.tongfei.progressbar.ProgressBarStyle; import org.gradle.api.GradleException; import org.gradle.api.Project; @@ -45,7 +47,6 @@ import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta; import net.fabricmc.loom.util.MirrorUtil; import net.fabricmc.loom.util.HashedDownloadUtil; -import net.fabricmc.loom.util.gradle.ProgressLoggerHelper; public class MinecraftAssetsProvider { public static void provide(MinecraftProviderImpl minecraftProvider, Project project) throws IOException { @@ -78,8 +79,8 @@ public class MinecraftAssetsProvider { HashedDownloadUtil.downloadIfInvalid(new URL(assetIndex.url()), assetsInfo, assetIndex.sha1(), project.getLogger(), false); } - Deque<ProgressLoggerHelper> loggers = new ConcurrentLinkedDeque<>(); - ExecutorService executor = Executors.newFixedThreadPool(Math.min(10, Math.max(Runtime.getRuntime().availableProcessors() / 2, 1))); + ExecutorService executor = Executors.newFixedThreadPool(Math.min(16, Math.max(Runtime.getRuntime().availableProcessors() * 2, 1))); + int toDownload = 0; AssetIndex index; @@ -91,72 +92,78 @@ public class MinecraftAssetsProvider { Map<String, AssetObject> parent = index.objects(); - for (Map.Entry<String, AssetObject> entry : parent.entrySet()) { - AssetObject object = entry.getValue(); - String sha1 = object.hash(); - String filename = "objects" + File.separator + sha1.substring(0, 2) + File.separator + sha1; - File file = new File(assets, filename); - - if (offline) { - if (file.exists()) { - project.getLogger().warn("Outdated asset " + entry.getKey()); - } else { - throw new GradleException("Asset " + entry.getKey() + " not found at " + file.getAbsolutePath()); - } - } else { - executor.execute(() -> { - final String[] assetName = {entry.getKey()}; - int end = assetName[0].lastIndexOf("/") + 1; + ProgressBar[] progressBar = {null}; - if (end > 0) { - assetName[0] = assetName[0].substring(end); + try { + for (Map.Entry<String, AssetObject> entry : parent.entrySet()) { + AssetObject object = entry.getValue(); + String sha1 = object.hash(); + String filename = "objects" + File.separator + sha1.substring(0, 2) + File.separator + sha1; + File file = new File(assets, filename); + + if (offline) { + if (file.exists()) { + project.getLogger().warn("Outdated asset " + entry.getKey()); + } else { + throw new GradleException("Asset " + entry.getKey() + " not found at " + file.getAbsolutePath()); + } + } else if (HashedDownloadUtil.requiresDownload(file, sha1, project.getLogger())) { + toDownload++; + + synchronized (progressBar) { + if (progressBar[0] == null) { + progressBar[0] = new ProgressBarBuilder() + .setConsumer(new DelegatingProgressBarConsumer(project.getLogger()::lifecycle)) + .setInitialMax(toDownload) + .setUpdateIntervalMillis(2000) + .setTaskName(":downloading assets") + .setStyle(ProgressBarStyle.ASCII) + .showSpeed() + .build(); + } + + progressBar[0].maxHint(toDownload); } - project.getLogger().debug("validating asset " + assetName[0]); - - final ProgressLoggerHelper[] progressLogger = new ProgressLoggerHelper[1]; + executor.execute(() -> { + String assetName = entry.getKey(); + int end = assetName.lastIndexOf("/") + 1; - try { - HashedDownloadUtil.downloadIfInvalid(new URL(MirrorUtil.getResourcesBase(project) + sha1.substring(0, 2) + "/" + sha1), file, sha1, project.getLogger(), true, () -> { - ProgressLoggerHelper logger = loggers.pollFirst(); + if (end > 0) { + assetName = assetName.substring(end); + } - if (logger == null) { - //Create a new logger if we need one - progressLogger[0] = ProgressLoggerHelper.getProgressFactory(project, MinecraftAssetsProvider.class.getName()); - progressLogger[0].start("Downloading assets...", "assets"); - } else { - // use a free logger if we can - progressLogger[0] = logger; - } + project.getLogger().debug(":downloading asset " + assetName); - project.getLogger().debug("downloading asset " + assetName[0]); - progressLogger[0].progress(String.format("%-30.30s", assetName[0]) + " - " + sha1); - }); - } catch (IOException e) { - throw new RuntimeException("Failed to download: " + assetName[0], e); - } + try { + HashedDownloadUtil.downloadIfInvalid(new URL(MirrorUtil.getResourcesBase(project) + sha1.substring(0, 2) + "/" + sha1), file, sha1, project.getLogger(), true, false); + } catch (IOException e) { + throw new RuntimeException("Failed to download: " + assetName, e); + } - if (progressLogger[0] != null) { - //Give this logger back if we used it - loggers.add(progressLogger[0]); - } - }); + synchronized (progressBar) { + progressBar[0].step(); + } + }); + } } - } - project.getLogger().info("Took " + stopwatch.stop() + " to iterate " + parent.size() + " asset index."); + project.getLogger().info("Took " + stopwatch.stop() + " to iterate " + parent.size() + " asset index."); - //Wait for the assets to all download - executor.shutdown(); + //Wait for the assets to all download + executor.shutdown(); - try { - if (executor.awaitTermination(2, TimeUnit.HOURS)) { - executor.shutdownNow(); + try { + if (executor.awaitTermination(2, TimeUnit.HOURS)) { + executor.shutdownNow(); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } finally { + if (progressBar[0] != null) { + progressBar[0].close(); } - } catch (InterruptedException e) { - throw new RuntimeException(e); } - - loggers.forEach(ProgressLoggerHelper::completed); } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/tr/OutputRemappingHandler.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/tr/OutputRemappingHandler.java new file mode 100644 index 00000000..5e2aa6a9 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/tr/OutputRemappingHandler.java @@ -0,0 +1,81 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.providers.minecraft.tr; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; +import java.util.function.BiConsumer; + +import dev.architectury.tinyremapper.InputTag; +import dev.architectury.tinyremapper.TinyRemapper; + +import net.fabricmc.loom.util.FileSystemUtil; +import net.fabricmc.loom.util.ThreadingUtils; + +public class OutputRemappingHandler { + public static void remap(TinyRemapper remapper, Path assets, Path output) throws IOException { + remap(remapper, assets, output, null); + } + + public static void remap(TinyRemapper remapper, Path assets, Path output, BiConsumer<String, byte[]> then) throws IOException { + remap(remapper, assets, output, then, (InputTag[]) null); + } + + public static void remap(TinyRemapper remapper, Path assets, Path output, BiConsumer<String, byte[]> then, InputTag... inputTags) throws IOException { + Files.copy(assets, output, StandardCopyOption.REPLACE_EXISTING); + + try (FileSystemUtil.Delegate system = FileSystemUtil.getJarFileSystem(output, true)) { + ThreadingUtils.TaskCompleter taskCompleter = ThreadingUtils.taskCompleter(); + + remapper.apply((path, bytes) -> { + if (path.startsWith("/")) path = path.substring(1); + + try { + Path fsPath = system.get().getPath(path + ".class"); + + if (fsPath.getParent() != null) { + Files.createDirectories(fsPath.getParent()); + } + + taskCompleter.add(() -> { + Files.write(fsPath, bytes, StandardOpenOption.CREATE); + }); + + if (then != null) { + then.accept(path, bytes); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }, inputTags); + + taskCompleter.complete(); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/sources/ForgeSourcesRemapper.java b/src/main/java/net/fabricmc/loom/configuration/sources/ForgeSourcesRemapper.java new file mode 100644 index 00000000..5b523153 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/sources/ForgeSourcesRemapper.java @@ -0,0 +1,259 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.sources; + +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; + +import org.apache.commons.io.output.NullOutputStream; +import org.cadixdev.lorenz.MappingSet; +import org.cadixdev.mercury.Mercury; +import org.cadixdev.mercury.remapper.MercuryRemapper; +import org.gradle.api.Project; +import org.gradle.api.artifacts.ResolvedArtifact; +import org.gradle.api.logging.LogLevel; +import org.gradle.api.logging.configuration.ShowStacktrace; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.build.ModCompileRemapper; +import net.fabricmc.loom.task.GenerateSourcesTask; +import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.DeletingFileVisitor; +import net.fabricmc.loom.util.FileSystemUtil; +import net.fabricmc.loom.util.SourceRemapper; +import net.fabricmc.loom.util.ThreadingUtils; +import net.fabricmc.loom.util.TinyRemapperHelper; +import net.fabricmc.loom.util.ZipUtils; +import net.fabricmc.lorenztiny.TinyMappingsReader; + +public class ForgeSourcesRemapper { + public static void addBaseForgeSources(Project project, boolean isOfficial) throws IOException { + Path sourcesJar = GenerateSourcesTask.getMappedJarFileWithSuffix(project, "-sources.jar", !isOfficial).toPath(); + + if (!Files.exists(sourcesJar)) { + addForgeSources(project, sourcesJar); + } + } + + public static void addForgeSources(Project project, Path sourcesJar) throws IOException { + try (FileSystemUtil.Delegate delegate = FileSystemUtil.getJarFileSystem(sourcesJar, true)) { + ThreadingUtils.TaskCompleter taskCompleter = ThreadingUtils.taskCompleter(); + + provideForgeSources(project, (path, bytes) -> { + Path fsPath = delegate.get().getPath(path); + + if (fsPath.getParent() != null) { + try { + Files.createDirectories(fsPath.getParent()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + taskCompleter.add(() -> { + Files.write(fsPath, bytes, StandardOpenOption.CREATE); + }); + }); + + taskCompleter.complete(); + } + } + + public static void provideForgeSources(Project project, BiConsumer<String, byte[]> consumer) throws IOException { + List<Path> forgeInstallerSources = new ArrayList<>(); + + for (ResolvedArtifact artifact : project.getConfigurations().getByName(Constants.Configurations.FORGE_INSTALLER).getResolvedConfiguration().getResolvedArtifacts()) { + File forgeInstallerSource = ModCompileRemapper.findSources(project.getDependencies(), artifact); + + if (forgeInstallerSource != null) { + forgeInstallerSources.add(forgeInstallerSource.toPath()); + } + } + + project.getLogger().lifecycle(":found {} forge source jars", forgeInstallerSources.size()); + Map<String, byte[]> forgeSources = extractSources(forgeInstallerSources); + project.getLogger().lifecycle(":extracted {} forge source classes", forgeSources.size()); + remapSources(project, forgeSources); + forgeSources.forEach(consumer); + } + + private static void remapSources(Project project, Map<String, byte[]> sources) throws IOException { + File tmpInput = File.createTempFile("tmpInputForgeSources", null); + tmpInput.delete(); + tmpInput.deleteOnExit(); + File tmpOutput = File.createTempFile("tmpOutputForgeSources", null); + tmpOutput.delete(); + tmpOutput.deleteOnExit(); + + try (FileSystemUtil.Delegate delegate = FileSystemUtil.getJarFileSystem(tmpInput, true)) { + ThreadingUtils.TaskCompleter taskCompleter = ThreadingUtils.taskCompleter(); + + for (Map.Entry<String, byte[]> entry : sources.entrySet()) { + Path path = delegate.get().getPath(entry.getKey()); + + if (path.getParent() != null) { + Files.createDirectories(path.getParent()); + } + + taskCompleter.add(() -> { + Files.write(path, entry.getValue(), StandardOpenOption.CREATE); + }); + } + + taskCompleter.complete(); + } + + PrintStream out = System.out; + PrintStream err = System.err; + + if (project.getGradle().getStartParameter().getShowStacktrace() == ShowStacktrace.INTERNAL_EXCEPTIONS && project.getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) >= 0) { + System.setOut(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM)); + System.setErr(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM)); + } + + remapForgeSourcesInner(project, tmpInput.toPath(), tmpOutput.toPath()); + + if (project.getGradle().getStartParameter().getShowStacktrace() == ShowStacktrace.INTERNAL_EXCEPTIONS && project.getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) >= 0) { + System.setOut(out); + System.setErr(err); + } + + tmpInput.delete(); + int[] failedToRemap = {0}; + + try (FileSystemUtil.Delegate delegate = FileSystemUtil.getJarFileSystem(tmpOutput, false)) { + ThreadingUtils.TaskCompleter taskCompleter = ThreadingUtils.taskCompleter(); + + for (Map.Entry<String, byte[]> entry : new HashSet<>(sources.entrySet())) { + taskCompleter.add(() -> { + Path path = delegate.get().getPath(entry.getKey()); + + if (Files.exists(path)) { + sources.put(entry.getKey(), Files.readAllBytes(path)); + } else { + sources.remove(entry.getKey()); + project.getLogger().error("Failed to remap sources for " + entry.getKey()); + failedToRemap[0]++; + } + }); + } + + taskCompleter.complete(); + } + + tmpOutput.delete(); + + if (failedToRemap[0] > 0) { + project.getLogger().error("Failed to remap {} forge sources", failedToRemap[0]); + } + } + + private static void remapForgeSourcesInner(Project project, Path tmpInput, Path tmpOutput) throws IOException { + LoomGradleExtension extension = LoomGradleExtension.get(project); + Mercury mercury = SourceRemapper.createMercuryWithClassPath(project, false); + + MappingSet mappings = new TinyMappingsReader(extension.getMappingsProvider().getMappingsWithSrg(), "srg", "named").read(); + + for (Map.Entry<String, String> entry : TinyRemapperHelper.JSR_TO_JETBRAINS.entrySet()) { + mappings.getOrCreateClassMapping(entry.getKey()).setDeobfuscatedName(entry.getValue()); + } + + Set<File> files = project.getConfigurations() + .detachedConfiguration(project.getDependencies().create(Constants.Dependencies.JETBRAINS_ANNOTATIONS + Constants.Dependencies.Versions.JETBRAINS_ANNOTATIONS)) + .resolve(); + + for (File file : files) { + mercury.getClassPath().add(file.toPath()); + } + + // Distinct and add the srg jar at the top, so it gets prioritized + mercury.getClassPath().add(0, extension.getMinecraftMappedProvider().getSrgJar().toPath()); + + if (extension.isForgeAndNotOfficial()) { + mercury.getClassPath().add(0, extension.getMinecraftMappedProvider().getForgeSrgJar().toPath()); + } + + List<Path> newClassPath = mercury.getClassPath().stream() + .distinct() + .filter(Files::isRegularFile) + .collect(Collectors.toList()); + mercury.getClassPath().clear(); + mercury.getClassPath().addAll(newClassPath); + + mercury.getProcessors().add(MercuryRemapper.create(mappings)); + boolean isSrcTmp = false; + + if (!Files.isDirectory(tmpInput)) { + Path tmpInput1 = tmpInput; + // create tmp directory + isSrcTmp = true; + tmpInput = Files.createTempDirectory("fabric-loom-src"); + ZipUtils.unpackAll(tmpInput1, tmpInput); + } + + try (FileSystemUtil.Delegate outputFs = FileSystemUtil.getJarFileSystem(tmpOutput, true)) { + Path outputFsRoot = outputFs.get().getPath("/"); + mercury.rewrite(tmpInput, outputFsRoot); + } catch (Exception e) { + project.getLogger().warn("Could not remap " + tmpInput + " fully!", e); + } + + if (isSrcTmp) { + Files.walkFileTree(tmpInput, new DeletingFileVisitor()); + } + } + + private static Map<String, byte[]> extractSources(List<Path> forgeInstallerSources) throws IOException { + Map<String, byte[]> sources = new ConcurrentHashMap<>(); + ThreadingUtils.TaskCompleter taskCompleter = ThreadingUtils.taskCompleter(); + + for (Path path : forgeInstallerSources) { + FileSystemUtil.Delegate system = FileSystemUtil.getJarFileSystem(path, false); + taskCompleter.onComplete(stopwatch -> system.close()); + + for (Path filePath : (Iterable<? extends Path>) Files.walk(system.get().getPath("/"))::iterator) { + if (Files.isRegularFile(filePath) && filePath.getFileName().toString().endsWith(".java")) { + taskCompleter.add(() -> sources.put(filePath.toString(), Files.readAllBytes(filePath))); + } + } + } + + taskCompleter.complete(); + return sources; + } +} diff --git a/src/main/java/net/fabricmc/loom/extension/ForgeExtensionImpl.java b/src/main/java/net/fabricmc/loom/extension/ForgeExtensionImpl.java new file mode 100644 index 00000000..25da6717 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/extension/ForgeExtensionImpl.java @@ -0,0 +1,136 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.extension; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import javax.inject.Inject; + +import org.gradle.api.Action; +import org.gradle.api.NamedDomainObjectContainer; +import org.gradle.api.Project; +import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.provider.Property; +import org.gradle.api.provider.SetProperty; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.api.ForgeExtensionAPI; +import net.fabricmc.loom.api.ForgeLocalMod; +import net.fabricmc.loom.configuration.ide.RunConfigSettings; + +public class ForgeExtensionImpl implements ForgeExtensionAPI { + private final LoomGradleExtension extension; + private final Property<Boolean> convertAccessWideners; + private final SetProperty<String> extraAccessWideners; + private final ConfigurableFileCollection accessTransformers; + private final SetProperty<String> mixinConfigs; + private final Property<Boolean> useCustomMixin; + private final List<String> dataGenMods = new ArrayList<>(); // not a property because it has custom adding logic + private final NamedDomainObjectContainer<ForgeLocalMod> localMods; + + @Inject + public ForgeExtensionImpl(Project project, LoomGradleExtension extension) { + this.extension = extension; + convertAccessWideners = project.getObjects().property(Boolean.class).convention(false); + extraAccessWideners = project.getObjects().setProperty(String.class).empty(); + accessTransformers = project.getObjects().fileCollection(); + mixinConfigs = project.getObjects().setProperty(String.class).empty(); + useCustomMixin = project.getObjects().property(Boolean.class).convention(true); + localMods = project.container(ForgeLocalMod.class, + baseName -> new ForgeLocalMod(project, baseName, new ArrayList<>())); + + // Create default mod from main source set + localMods(mod -> mod.create("main").add("main")); + } + + @Override + public Property<Boolean> getConvertAccessWideners() { + return convertAccessWideners; + } + + @Override + public SetProperty<String> getExtraAccessWideners() { + return extraAccessWideners; + } + + @Override + public ConfigurableFileCollection getAccessTransformers() { + return accessTransformers; + } + + @Override + public void accessTransformer(Object file) { + accessTransformers.from(file); + } + + @Override + public SetProperty<String> getMixinConfigs() { + return mixinConfigs; + } + + @Override + public void mixinConfigs(String... mixinConfigs) { + this.mixinConfigs.addAll(mixinConfigs); + } + + @Override + public Property<Boolean> getUseCustomMixin() { + return useCustomMixin; + } + + @Override + public List<String> getDataGenMods() { + // unmod list prevents uncontrolled additions (we want to create the run config too) + return Collections.unmodifiableList(dataGenMods); + } + + @SuppressWarnings("Convert2Lambda") + @Override + public void dataGen(Action<DataGenConsumer> action) { + action.execute(new DataGenConsumer() { + @Override + public void mod(String... modIds) { + dataGenMods.addAll(Arrays.asList(modIds)); + + if (modIds.length > 0 && extension.getRunConfigs().findByName("data") == null) { + extension.getRunConfigs().create("data", RunConfigSettings::data); + } + } + }); + } + + @Override + public void localMods(Action<NamedDomainObjectContainer<ForgeLocalMod>> action) { + action.execute(localMods); + } + + @Override + public NamedDomainObjectContainer<ForgeLocalMod> getLocalMods() { + return localMods; + } +} diff --git a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java index f833e52e..d3f6d671 100644 --- a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java +++ b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java @@ -24,6 +24,14 @@ package net.fabricmc.loom.extension; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.function.Consumer; + +import com.google.common.base.Suppliers; import org.gradle.api.Action; import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.Project; @@ -32,13 +40,18 @@ import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; +import org.gradle.api.provider.Provider; import org.gradle.api.publish.maven.MavenPublication; +import net.fabricmc.loom.api.ForgeExtensionAPI; import net.fabricmc.loom.api.LoomGradleExtensionAPI; import net.fabricmc.loom.api.MixinExtensionAPI; import net.fabricmc.loom.api.decompilers.LoomDecompiler; +import net.fabricmc.loom.api.decompilers.architectury.ArchitecturyLoomDecompiler; import net.fabricmc.loom.api.mappings.layered.spec.LayeredMappingSpecBuilder; +import net.fabricmc.loom.configuration.ide.RunConfig; import net.fabricmc.loom.configuration.ide.RunConfigSettings; +import net.fabricmc.loom.configuration.launch.LaunchProviderSettings; import net.fabricmc.loom.configuration.mods.ModVersionParser; import net.fabricmc.loom.configuration.processors.JarProcessor; import net.fabricmc.loom.configuration.providers.mappings.GradleMappingContext; @@ -46,11 +59,15 @@ import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingSpec; import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingSpecBuilderImpl; import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingsDependency; import net.fabricmc.loom.util.DeprecationHelper; +import net.fabricmc.loom.util.ModPlatform; /** * This class implements the public extension api. */ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionAPI { + private static final String FORGE_PROPERTY = "loom.forge"; + private static final String PLATFORM_PROPERTY = "loom.platform"; + protected final DeprecationHelper deprecationHelper; protected final ListProperty<LoomDecompiler> decompilers; protected final ListProperty<JarProcessor> jarProcessors; @@ -67,6 +84,17 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA private NamedDomainObjectContainer<RunConfigSettings> runConfigs; + // =================== + // Architectury Loom + // =================== + private final ListProperty<ArchitecturyLoomDecompiler> archDecompilers; + private Provider<ModPlatform> platform; + private boolean silentMojangMappingsLicense = false; + public Boolean generateSrgTiny = null; + private final List<String> tasksBeforeRun = Collections.synchronizedList(new ArrayList<>()); + public final List<Consumer<RunConfig>> settingsPostEdit = new ArrayList<>(); + private NamedDomainObjectContainer<LaunchProviderSettings> launchConfigs; + protected LoomGradleExtensionApiImpl(Project project, LoomFiles directories) { this.runConfigs = project.container(RunConfigSettings.class, baseName -> new RunConfigSettings(project, baseName)); @@ -92,6 +120,26 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA this.versionParser = new ModVersionParser(project); this.deprecationHelper = new DeprecationHelper.ProjectBased(project); + this.platform = project.provider(Suppliers.memoize(() -> { + Object platformProperty = project.findProperty(PLATFORM_PROPERTY); + + if (platformProperty != null) { + return ModPlatform.valueOf(Objects.toString(platformProperty).toUpperCase(Locale.ROOT)); + } + + Object forgeProperty = project.findProperty(FORGE_PROPERTY); + + if (forgeProperty != null) { + project.getLogger().warn("Project " + project.getPath() + " is using property " + FORGE_PROPERTY + " to enable forge mode. Please use '" + PLATFORM_PROPERTY + " = forge' instead!"); + return Boolean.parseBoolean(Objects.toString(forgeProperty)) ? ModPlatform.FORGE : ModPlatform.FABRIC; + } + + return ModPlatform.FABRIC; + })::get); + this.launchConfigs = project.container(LaunchProviderSettings.class, + baseName -> new LaunchProviderSettings(project, baseName)); + this.archDecompilers = project.getObjects().listProperty(ArchitecturyLoomDecompiler.class) + .empty(); } @Override @@ -121,12 +169,14 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA @Override public Dependency layered(Action<LayeredMappingSpecBuilder> action) { - LayeredMappingSpecBuilderImpl builder = new LayeredMappingSpecBuilderImpl(); + LayeredMappingSpecBuilderImpl builder = new LayeredMappingSpecBuilderImpl(this); action.execute(builder); LayeredMappingSpec builtSpec = builder.build(); return new LayeredMappingsDependency(getProject(), new GradleMappingContext(getProject(), builtSpec.getVersion().replace("+", "_").replace(".", "_")), builtSpec, builtSpec.getVersion()); } + protected abstract String getMinecraftVersion(); + @Override public Property<Boolean> getRemapArchives() { return remapArchives; @@ -186,6 +236,65 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA net.fabricmc.loom.configuration.MavenPublication.excludePublication(publication); } + @Override + public void silentMojangMappingsLicense() { + this.silentMojangMappingsLicense = true; + } + + @Override + public boolean isSilentMojangMappingsLicenseEnabled() { + return silentMojangMappingsLicense; + } + + @Override + public Provider<ModPlatform> getPlatform() { + return platform; + } + + @Override + public void setGenerateSrgTiny(Boolean generateSrgTiny) { + this.generateSrgTiny = generateSrgTiny; + } + + @Override + public boolean shouldGenerateSrgTiny() { + if (generateSrgTiny != null) { + return generateSrgTiny; + } + + return isForge(); + } + + @Override + public void launches(Action<NamedDomainObjectContainer<LaunchProviderSettings>> action) { + action.execute(launchConfigs); + } + + @Override + public NamedDomainObjectContainer<LaunchProviderSettings> getLaunchConfigs() { + return launchConfigs; + } + + @Override + public List<String> getTasksBeforeRun() { + return tasksBeforeRun; + } + + @Override + public List<Consumer<RunConfig>> getSettingsPostEdit() { + return settingsPostEdit; + } + + @Override + public void forge(Action<ForgeExtensionAPI> action) { + action.execute(getForge()); + } + + @Override + public ListProperty<ArchitecturyLoomDecompiler> getArchGameDecompilers() { + return archDecompilers; + } + // This is here to ensure that LoomGradleExtensionApiImpl compiles without any unimplemented methods private final class EnsureCompile extends LoomGradleExtensionApiImpl { private EnsureCompile() { @@ -212,5 +321,15 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA public MixinExtension getMixin() { throw new RuntimeException("Yeah... something is really wrong"); } + + @Override + protected String getMinecraftVersion() { + throw new RuntimeException("Yeah... something is really wrong"); + } + + @Override + public ForgeExtensionAPI getForge() { + throw new RuntimeException("Yeah... something is really wrong"); + } } } diff --git a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java index da0fca73..9b93c066 100644 --- a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java +++ b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java @@ -35,6 +35,7 @@ import java.util.Objects; import java.util.Set; import java.util.function.Supplier; +import com.google.common.base.Suppliers; import org.cadixdev.lorenz.MappingSet; import org.cadixdev.mercury.Mercury; import org.gradle.api.NamedDomainObjectProvider; @@ -43,16 +44,20 @@ import org.gradle.api.artifacts.Configuration; import org.gradle.api.file.ConfigurableFileCollection; import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.api.ForgeExtensionAPI; import net.fabricmc.loom.configuration.InstallerData; import net.fabricmc.loom.configuration.LoomDependencyManager; import net.fabricmc.loom.configuration.accesswidener.AccessWidenerFile; import net.fabricmc.loom.configuration.processors.JarProcessorManager; +import net.fabricmc.loom.util.ModPlatform; +import net.fabricmc.loom.util.function.LazyBool; public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implements LoomGradleExtension { private final Project project; private final MixinExtension mixinApExtension; private final LoomFiles loomFiles; private final ConfigurableFileCollection unmappedMods; + private final Supplier<ForgeExtensionAPI> forgeExtension; private final Set<File> mixinMappings = Collections.synchronizedSet(new HashSet<>()); private final MappingSet[] srcMappingCache = new MappingSet[2]; @@ -64,6 +69,12 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen private JarProcessorManager jarProcessorManager; private InstallerData installerData; + // +-------------------+ + // | Architectury Loom | + // +-------------------+ + private static final String INCLUDE_PROPERTY = "loom.forge.include"; + private final LazyBool supportsInclude; + public LoomGradleExtensionImpl(Project project, LoomFiles files) { super(project, files); this.project = project; @@ -71,6 +82,8 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen this.mixinApExtension = project.getObjects().newInstance(MixinExtensionImpl.class, project); this.loomFiles = files; this.unmappedMods = project.files(); + this.forgeExtension = Suppliers.memoize(() -> isForge() ? project.getObjects().newInstance(ForgeExtensionImpl.class, project, this) : null); + this.supportsInclude = new LazyBool(() -> Boolean.parseBoolean(Objects.toString(project.findProperty(INCLUDE_PROPERTY)))); } @Override @@ -117,11 +130,13 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen @Override public MappingSet getOrCreateSrcMappingCache(int id, Supplier<MappingSet> factory) { + if (id < 0 || id >= srcMappingCache.length) return factory.get(); return srcMappingCache[id] != null ? srcMappingCache[id] : (srcMappingCache[id] = factory.get()); } @Override public Mercury getOrCreateSrcMercuryCache(int id, Supplier<Mercury> factory) { + if (id < 0 || id >= srcMercuryCache.length) return factory.get(); return srcMercuryCache[id] != null ? srcMercuryCache[id] : (srcMercuryCache[id] = factory.get()); } @@ -182,4 +197,20 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen public void addTransitiveAccessWideners(List<AccessWidenerFile> accessWidenerFiles) { transitiveAccessWideners.addAll(accessWidenerFiles); } + + @Override + protected String getMinecraftVersion() { + return getMinecraftProvider().minecraftVersion(); + } + + @Override + public ForgeExtensionAPI getForge() { + ModPlatform.assertPlatform(this, ModPlatform.FORGE); + return forgeExtension.get(); + } + + @Override + public boolean supportsInclude() { + return !isForge() || supportsInclude.getAsBoolean(); + } } diff --git a/src/main/java/net/fabricmc/loom/extension/MinecraftGradleExtension.java b/src/main/java/net/fabricmc/loom/extension/MinecraftGradleExtension.java index 4ffea879..bd33fd62 100644 --- a/src/main/java/net/fabricmc/loom/extension/MinecraftGradleExtension.java +++ b/src/main/java/net/fabricmc/loom/extension/MinecraftGradleExtension.java @@ -24,6 +24,9 @@ package net.fabricmc.loom.extension; +import java.util.List; +import java.util.function.Consumer; + import org.gradle.api.Action; import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.artifacts.Dependency; @@ -31,15 +34,21 @@ import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; +import org.gradle.api.provider.Provider; import org.gradle.api.publish.maven.MavenPublication; +import net.fabricmc.loom.api.ForgeExtensionAPI; import net.fabricmc.loom.api.LoomGradleExtensionAPI; import net.fabricmc.loom.api.MixinExtensionAPI; import net.fabricmc.loom.api.decompilers.LoomDecompiler; +import net.fabricmc.loom.api.decompilers.architectury.ArchitecturyLoomDecompiler; import net.fabricmc.loom.api.mappings.layered.spec.LayeredMappingSpecBuilder; +import net.fabricmc.loom.configuration.ide.RunConfig; import net.fabricmc.loom.configuration.ide.RunConfigSettings; +import net.fabricmc.loom.configuration.launch.LaunchProviderSettings; import net.fabricmc.loom.configuration.processors.JarProcessor; import net.fabricmc.loom.util.DeprecationHelper; +import net.fabricmc.loom.util.ModPlatform; public class MinecraftGradleExtension implements LoomGradleExtensionAPI { private final LoomGradleExtensionAPI parent; @@ -162,4 +171,76 @@ public class MinecraftGradleExtension implements LoomGradleExtensionAPI { reportDeprecation(); return parent.getIntermediaryUrl(); } + + @Override + public ListProperty<ArchitecturyLoomDecompiler> getArchGameDecompilers() { + reportDeprecation(); + return parent.getArchGameDecompilers(); + } + + @Override + public void silentMojangMappingsLicense() { + reportDeprecation(); + parent.silentMojangMappingsLicense(); + } + + @Override + public boolean isSilentMojangMappingsLicenseEnabled() { + reportDeprecation(); + return parent.isSilentMojangMappingsLicenseEnabled(); + } + + @Override + public Provider<ModPlatform> getPlatform() { + reportDeprecation(); + return parent.getPlatform(); + } + + @Override + public void setGenerateSrgTiny(Boolean generateSrgTiny) { + reportDeprecation(); + parent.setGenerateSrgTiny(generateSrgTiny); + } + + @Override + public boolean shouldGenerateSrgTiny() { + reportDeprecation(); + return parent.shouldGenerateSrgTiny(); + } + + @Override + public void launches(Action<NamedDomainObjectContainer<LaunchProviderSettings>> action) { + reportDeprecation(); + parent.launches(action); + } + + @Override + public NamedDomainObjectContainer<LaunchProviderSettings> getLaunchConfigs() { + reportDeprecation(); + return parent.getLaunchConfigs(); + } + + @Override + public List<String> getTasksBeforeRun() { + reportDeprecation(); + return parent.getTasksBeforeRun(); + } + + @Override + public List<Consumer<RunConfig>> getSettingsPostEdit() { + reportDeprecation(); + return parent.getSettingsPostEdit(); + } + + @Override + public ForgeExtensionAPI getForge() { + reportDeprecation(); + return parent.getForge(); + } + + @Override + public void forge(Action<ForgeExtensionAPI> action) { + reportDeprecation(); + parent.forge(action); + } } diff --git a/src/main/java/net/fabricmc/loom/extension/MixinExtensionApiImpl.java b/src/main/java/net/fabricmc/loom/extension/MixinExtensionApiImpl.java index cf958d99..d0e3950d 100644 --- a/src/main/java/net/fabricmc/loom/extension/MixinExtensionApiImpl.java +++ b/src/main/java/net/fabricmc/loom/extension/MixinExtensionApiImpl.java @@ -44,6 +44,7 @@ public abstract class MixinExtensionApiImpl implements MixinExtensionAPI { public MixinExtensionApiImpl(Project project) { this.project = Objects.requireNonNull(project); this.useMixinAp = project.getObjects().property(Boolean.class) + // .convention(project.provider(() -> LoomGradleExtension.get(project).isForge())); .convention(true); } diff --git a/src/main/java/net/fabricmc/loom/extension/MixinExtensionImpl.java b/src/main/java/net/fabricmc/loom/extension/MixinExtensionImpl.java index 074251e4..a96c911a 100644 --- a/src/main/java/net/fabricmc/loom/extension/MixinExtensionImpl.java +++ b/src/main/java/net/fabricmc/loom/extension/MixinExtensionImpl.java @@ -68,6 +68,11 @@ public class MixinExtensionImpl extends MixinExtensionApiImpl implements MixinEx private String getDefaultMixinRefmapName() { String defaultRefmapName = project.getConvention().getPlugin(BasePluginConvention.class).getArchivesBaseName() + "-refmap.json"; + + if (project.getRootProject() != project) { + defaultRefmapName = project.getConvention().getPlugin(BasePluginConvention.class).getArchivesBaseName() + "-" + project.getPath().replaceFirst(":", "").replace(':', '_') + "-refmap.json"; + } + project.getLogger().info("Could not find refmap definition, will be using default name: " + defaultRefmapName); return defaultRefmapName; } diff --git a/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java b/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java index 6bb63e53..f3c2b7bd 100644 --- a/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java +++ b/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java @@ -51,6 +51,7 @@ public abstract class AbstractRunTask extends JavaExec { @Override public void exec() { setWorkingDir(new File(getProject().getRootDir(), config.runDir)); + environment(config.envVariables); super.exec(); } diff --git a/src/main/java/net/fabricmc/loom/task/ArchitecturyGenerateSourcesTask.java b/src/main/java/net/fabricmc/loom/task/ArchitecturyGenerateSourcesTask.java new file mode 100644 index 00000000..402cee1e --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/ArchitecturyGenerateSourcesTask.java @@ -0,0 +1,96 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.task; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; + +import javax.inject.Inject; + +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.provider.MapProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.TaskAction; + +import net.fabricmc.loom.api.decompilers.DecompilationMetadata; +import net.fabricmc.loom.api.decompilers.architectury.ArchitecturyLoomDecompiler; +import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.OperatingSystem; + +public abstract class ArchitecturyGenerateSourcesTask extends AbstractLoomTask { + private final ArchitecturyLoomDecompiler decompiler; + + @InputFile + public abstract RegularFileProperty getInputJar(); + + @Input + public abstract MapProperty<String, String> getOptions(); + + @Inject + public ArchitecturyGenerateSourcesTask(ArchitecturyLoomDecompiler decompiler) { + this.decompiler = decompiler; + getOutputs().upToDateWhen((o) -> false); + getOptions().finalizeValueOnRead(); + } + + @TaskAction + public void run() throws IOException { + if (!OperatingSystem.is64Bit()) { + throw new UnsupportedOperationException("GenSources task requires a 64bit JVM to run due to the memory requirements."); + } + + // TODO: Need a good way to not keep a duplicated code for this + Path compiledJar = getInputJar().get().getAsFile().toPath(); + Path runtimeJar = getExtension().getMappingsProvider().mappedProvider.getMappedJar().toPath(); + Path sourcesDestination = GenerateSourcesTask.getMappedJarFileWithSuffix(getProject(), "-sources.jar").toPath(); + Path linemapDestination = GenerateSourcesTask.getMappedJarFileWithSuffix(getProject(), "-sources.lmap").toPath(); + DecompilationMetadata metadata = new DecompilationMetadata( + Runtime.getRuntime().availableProcessors(), + GenerateSourcesTask.getMappings(getProject(), getExtension()), + GenerateSourcesTask.DecompileAction.toPaths(getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES)), + getLogger()::info, + getOptions().get() + ); + + decompiler.create(getProject()).decompile(compiledJar, sourcesDestination, linemapDestination, metadata); + + // Apply linemap + if (Files.exists(linemapDestination)) { + Path linemapJar = GenerateSourcesTask.getMappedJarFileWithSuffix(getProject(), "-linemapped.jar").toPath(); + + try { + GenerateSourcesTask.DecompileAction.remapLineNumbers(getLogger()::info, runtimeJar, linemapDestination, linemapJar); + Files.copy(linemapJar, runtimeJar, StandardCopyOption.REPLACE_EXISTING); + Files.delete(linemapJar); + } catch (Exception e) { + throw new RuntimeException("Could not remap line numbers", e); + } + } + } +} diff --git a/src/main/java/net/fabricmc/loom/task/GenEclipseRunsTask.java b/src/main/java/net/fabricmc/loom/task/GenEclipseRunsTask.java index 1e529bdc..b430f5cc 100644 --- a/src/main/java/net/fabricmc/loom/task/GenEclipseRunsTask.java +++ b/src/main/java/net/fabricmc/loom/task/GenEclipseRunsTask.java @@ -41,6 +41,7 @@ public class GenEclipseRunsTask extends AbstractLoomTask { public void genRuns() throws IOException { EclipseModel eclipseModel = getProject().getExtensions().getByType(EclipseModel.class); LoomGradleExtension extension = getExtension(); + File dataRunConfigs = new File(getProject().getRootDir(), eclipseModel.getProject().getName() + "_data.launch"); for (RunConfigSettings settings : extension.getRunConfigs()) { if (!settings.isIdeConfigGenerated()) { diff --git a/src/main/java/net/fabricmc/loom/task/GenVsCodeProjectTask.java b/src/main/java/net/fabricmc/loom/task/GenVsCodeProjectTask.java index ab6fc5fa..476dcd15 100644 --- a/src/main/java/net/fabricmc/loom/task/GenVsCodeProjectTask.java +++ b/src/main/java/net/fabricmc/loom/task/GenVsCodeProjectTask.java @@ -29,12 +29,20 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import org.apache.commons.io.FileUtils; +import org.apache.tools.ant.taskdefs.condition.Os; import org.gradle.api.Project; import org.gradle.api.tasks.TaskAction; +import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.LoomGradlePlugin; import net.fabricmc.loom.configuration.ide.RunConfig; import net.fabricmc.loom.configuration.ide.RunConfigSettings; @@ -46,8 +54,12 @@ import net.fabricmc.loom.configuration.ide.RunConfigSettings; public class GenVsCodeProjectTask extends AbstractLoomTask { @TaskAction public void genRuns() { - Project project = getProject(); - File projectDir = project.file(".vscode"); + clean(getProject()); + generate(getProject()); + } + + public static void clean(Project project) { + File projectDir = project.getRootProject().file(".vscode"); if (!projectDir.exists()) { projectDir.mkdir(); @@ -58,15 +70,39 @@ public class GenVsCodeProjectTask extends AbstractLoomTask { if (launchJson.exists()) { launchJson.delete(); } + } + + public static void generate(Project project) { + LoomGradleExtension extension = LoomGradleExtension.get(project); + File projectDir = project.getRootProject().file(".vscode"); + + if (!projectDir.exists()) { + projectDir.mkdir(); + } + + File launchJson = new File(projectDir, "launch.json"); + File tasksJson = new File(projectDir, "tasks.json"); + + Gson gson = new GsonBuilder().setPrettyPrinting().create(); - VsCodeLaunch launch = new VsCodeLaunch(); + VsCodeLaunch launch; - for (RunConfigSettings settings : getExtension().getRunConfigs()) { + if (launchJson.exists()) { + try { + launch = gson.fromJson(FileUtils.readFileToString(launchJson, StandardCharsets.UTF_8), VsCodeLaunch.class); + } catch (IOException e) { + throw new RuntimeException("Failed to read launch.json", e); + } + } else { + launch = new VsCodeLaunch(); + } + + for (RunConfigSettings settings : extension.getRunConfigs()) { if (!settings.isIdeConfigGenerated()) { continue; } - launch.add(RunConfig.runConfig(project, settings)); + launch.add(project, RunConfig.runConfig(project, settings)); settings.makeRunDir(); } @@ -77,19 +113,71 @@ public class GenVsCodeProjectTask extends AbstractLoomTask { } catch (IOException e) { throw new RuntimeException("Failed to write launch.json", e); } + + VsCodeTasks tasks; + + if (tasksJson.exists()) { + try { + tasks = gson.fromJson(FileUtils.readFileToString(tasksJson, StandardCharsets.UTF_8), VsCodeTasks.class); + } catch (IOException e) { + throw new RuntimeException("Failed to read launch.json", e); + } + } else { + tasks = new VsCodeTasks(); + } + + for (VsCodeConfiguration configuration : launch.configurations) { + if (configuration.preLaunchTask != null && configuration.tasksBeforeRun != null) { + String prefix = Os.isFamily(Os.FAMILY_WINDOWS) ? "gradlew.bat" : "./gradlew"; + tasks.add(new VsCodeTask(configuration.preLaunchTask, prefix + " " + configuration.tasksBeforeRun.stream() + .map(s -> { + int i = s.indexOf('/'); + return i == -1 ? s : s.substring(i + 1); + }).collect(Collectors.joining(" ")), "shell", new String[0])); + } + } + + if (!tasks.tasks.isEmpty()) { + String jsonTasks = gson.toJson(tasks); + + try { + FileUtils.writeStringToFile(tasksJson, jsonTasks, StandardCharsets.UTF_8); + } catch (IOException e) { + throw new RuntimeException("Failed to write tasks.json", e); + } + } } - private class VsCodeLaunch { + private static class VsCodeLaunch { public String version = "0.2.0"; public List<VsCodeConfiguration> configurations = new ArrayList<>(); - public void add(RunConfig runConfig) { - configurations.add(new VsCodeConfiguration(runConfig)); + public void add(Project project, RunConfig runConfig) { + if (configurations.stream().noneMatch(config -> Objects.equals(config.name, runConfig.configName))) { + VsCodeConfiguration configuration = new VsCodeConfiguration(project, runConfig); + configurations.add(configuration); + + if (!configuration.tasksBeforeRun.isEmpty()) { + configuration.preLaunchTask = "generated_" + runConfig.configName; + } + } + } + } + + private static class VsCodeTasks { + public String version = "2.0.0"; + public List<VsCodeTask> tasks = new ArrayList<>(); + + public void add(VsCodeTask vsCodeTask) { + if (tasks.stream().noneMatch(task -> Objects.equals(task.label, vsCodeTask.label))) { + tasks.add(vsCodeTask); + } } } @SuppressWarnings("unused") - private class VsCodeConfiguration { + private static class VsCodeConfiguration { + public transient Project project; public String type = "java"; public String name; public String request = "launch"; @@ -99,21 +187,43 @@ public class GenVsCodeProjectTask extends AbstractLoomTask { public String mainClass; public String vmArgs; public String args; + public Map<String, String> env = new LinkedHashMap<>(); + public transient List<String> tasksBeforeRun = new ArrayList<>(); + public String preLaunchTask = null; + public String projectName = null; - VsCodeConfiguration(RunConfig runConfig) { + VsCodeConfiguration(Project project, RunConfig runConfig) { this.name = runConfig.configName; this.mainClass = runConfig.mainClass; this.vmArgs = RunConfig.joinArguments(runConfig.vmArgs); this.args = RunConfig.joinArguments(runConfig.programArgs); this.cwd = "${workspaceFolder}/" + runConfig.runDir; + this.projectName = runConfig.vscodeProjectName; + this.env.putAll(runConfig.envVariables); + this.tasksBeforeRun.addAll(runConfig.vscodeBeforeRun); - if (getProject().getRootProject() != getProject()) { - Path rootPath = getProject().getRootDir().toPath(); - Path projectPath = getProject().getProjectDir().toPath(); + if (project.getRootProject() != project) { + Path rootPath = project.getRootDir().toPath(); + Path projectPath = project.getProjectDir().toPath(); String relativePath = rootPath.relativize(projectPath).toString(); this.cwd = "${workspaceFolder}/%s/%s".formatted(relativePath, runConfig.runDir); } } } + + private static class VsCodeTask { + public String label; + public String command; + public String type; + public String[] args; + public String group = "build"; + + VsCodeTask(String label, String command, String type, String[] args) { + this.label = label; + this.command = command; + this.type = type; + this.args = args; + } + } } diff --git a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java index 9a127d86..ab4f6929 100644 --- a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java +++ b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java @@ -41,7 +41,9 @@ import java.util.stream.Collectors; import javax.inject.Inject; +import org.gradle.api.Project; import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.file.FileCollection; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Property; @@ -146,7 +148,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { params.getSourcesDestinationJar().set(getMappedJarFileWithSuffix("-sources.jar")); params.getLinemap().set(getMappedJarFileWithSuffix("-sources.lmap")); params.getLinemapJar().set(getMappedJarFileWithSuffix("-linemapped.jar")); - params.getMappings().set(getMappings().toFile()); + params.getMappings().set(getMappings(getProject(), getExtension()).toFile()); if (ipcPath != null) { params.getIPCPath().set(ipcPath.toFile()); @@ -271,7 +273,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { } } - private void remapLineNumbers(IOStringConsumer logger, Path oldCompiledJar, Path linemap, Path linemappedJarDestination) throws IOException { + static void remapLineNumbers(IOStringConsumer logger, Path oldCompiledJar, Path linemap, Path linemappedJarDestination) throws IOException { LineNumberRemapper remapper = new LineNumberRemapper(); remapper.readMappings(linemap.toFile()); @@ -282,14 +284,26 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { } private Collection<Path> getLibraries() { - return getParameters().getClassPath().getFiles().stream().map(File::toPath).collect(Collectors.toSet()); + return toPaths(getParameters().getClassPath()); + } + + static Collection<Path> toPaths(FileCollection files) { + return files.getFiles().stream().map(File::toPath).collect(Collectors.toSet()); } } private File getMappedJarFileWithSuffix(String suffix) { - LoomGradleExtension extension = LoomGradleExtension.get(getProject()); + return getMappedJarFileWithSuffix(getProject(), suffix); + } + + public static File getMappedJarFileWithSuffix(Project project, String suffix) { + return getMappedJarFileWithSuffix(project, suffix, false); + } + + public static File getMappedJarFileWithSuffix(Project project, String suffix, boolean forgeJar) { + LoomGradleExtension extension = LoomGradleExtension.get(project); MappingsProviderImpl mappingsProvider = extension.getMappingsProvider(); - File mappedJar = mappingsProvider.mappedProvider.getMappedJar(); + File mappedJar = forgeJar ? mappingsProvider.mappedProvider.getForgeMappedJar() : mappingsProvider.mappedProvider.getMappedJar(); String path = mappedJar.getAbsolutePath(); if (!path.toLowerCase(Locale.ROOT).endsWith(".jar")) { @@ -299,11 +313,11 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { return new File(path.substring(0, path.length() - 4) + suffix); } - private Path getMappings() { - Path baseMappings = getExtension().getMappingsProvider().tinyMappings; + static Path getMappings(Project project, LoomGradleExtension extension) { + Path baseMappings = extension.isForge() ? extension.getMappingsProvider().tinyMappingsWithSrg : extension.getMappingsProvider().tinyMappings; - if (getExtension().getEnableTransitiveAccessWideners().get()) { - List<AccessWidenerFile> accessWideners = getExtension().getTransitiveAccessWideners(); + if (extension.getEnableTransitiveAccessWideners().get()) { + List<AccessWidenerFile> accessWideners = extension.getTransitiveAccessWideners(); if (accessWideners.isEmpty()) { return baseMappings; @@ -317,7 +331,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { throw new RuntimeException("Failed to create temp file", e); } - TransitiveAccessWidenerMappingsProcessor.process(baseMappings, outputMappings, accessWideners, getProject().getLogger()); + TransitiveAccessWidenerMappingsProcessor.process(baseMappings, outputMappings, accessWideners, project.getLogger()); return outputMappings; } diff --git a/src/main/java/net/fabricmc/loom/task/LoomTasks.java b/src/main/java/net/fabricmc/loom/task/LoomTasks.java index e914046f..47ea0eb6 100644 --- a/src/main/java/net/fabricmc/loom/task/LoomTasks.java +++ b/src/main/java/net/fabricmc/loom/task/LoomTasks.java @@ -25,6 +25,8 @@ package net.fabricmc.loom.task; import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; import com.google.common.base.Preconditions; import org.gradle.api.Project; @@ -32,7 +34,9 @@ import org.gradle.api.tasks.TaskContainer; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.api.decompilers.LoomDecompiler; +import net.fabricmc.loom.api.decompilers.architectury.ArchitecturyLoomDecompiler; import net.fabricmc.loom.configuration.ide.RunConfigSettings; +import net.fabricmc.loom.configuration.ide.SetupIntelijRunConfigs; import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; import net.fabricmc.loom.util.Constants; @@ -65,6 +69,7 @@ public final class LoomTasks { registerIDETasks(tasks); registerRunTasks(tasks, project); + registerLaunchSettings(project); registerDecompileTasks(tasks, project); } @@ -91,6 +96,19 @@ public final class LoomTasks { t.dependsOn("downloadAssets"); t.setGroup(Constants.TaskGroup.IDE); }); + + tasks.register("genIntelliJRuns", AbstractLoomTask.class, t -> { + t.setDescription("Generates IntelliJ IDEA launch configurations."); + t.dependsOn("downloadAssets"); + t.setGroup("ide"); + t.doLast(task -> { + try { + SetupIntelijRunConfigs.generate(task.getProject(), true); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + }); } private static void registerRunTasks(TaskContainer tasks, Project project) { @@ -115,6 +133,17 @@ public final class LoomTasks { extension.getRunConfigs().create("server", RunConfigSettings::server); } + private static void registerLaunchSettings(Project project) { + LoomGradleExtension extension = LoomGradleExtension.get(project); + Preconditions.checkArgument(extension.getLaunchConfigs().size() == 0, "Launch configurations must not be registered before loom"); + extension.getLaunchConfigs().create("client"); + extension.getLaunchConfigs().create("server"); + + if (extension.isForge()) { + extension.getLaunchConfigs().create("data"); + } + } + private static void registerDecompileTasks(TaskContainer tasks, Project project) { LoomGradleExtension extension = LoomGradleExtension.get(project); @@ -160,6 +189,20 @@ public final class LoomTasks { }); } + for (ArchitecturyLoomDecompiler decompiler : extension.getArchGameDecompilers().get()) { + String taskName = "genSourcesWith" + decompiler.name(); + // Decompiler will be passed to the constructor of ArchitecturyGenerateSourcesTask + tasks.register(taskName, ArchitecturyGenerateSourcesTask.class, decompiler).configure(task -> { + task.setDescription("Decompile minecraft using %s.".formatted(decompiler.name())); + task.setGroup(Constants.TaskGroup.FABRIC); + task.getInputJar().set(inputJar); + + if (mappingsProvider.hasUnpickDefinitions()) { + task.dependsOn(tasks.getByName("unpickJar")); + } + }); + } + tasks.register("genSources", task -> { task.setDescription("Decompile minecraft using the default decompiler."); task.setGroup(Constants.TaskGroup.FABRIC); diff --git a/src/main/java/net/fabricmc/loom/task/MigrateMappingsTask.java b/src/main/java/net/fabricmc/loom/task/MigrateMappingsTask.java index c976dded..f7181637 100644 --- a/src/main/java/net/fabricmc/loom/task/MigrateMappingsTask.java +++ b/src/main/java/net/fabricmc/loom/task/MigrateMappingsTask.java @@ -101,7 +101,7 @@ public class MigrateMappingsTask extends AbstractLoomTask { try { MemoryMappingTree currentMappings = mappingsProvider.getMappings(); MemoryMappingTree targetMappings = getMappings(mappings); - migrateMappings(project, extension.getMinecraftMappedProvider(), inputDir, outputDir, currentMappings, targetMappings); + migrateMappings(project, extension, extension.getMinecraftMappedProvider(), inputDir, outputDir, currentMappings, targetMappings); project.getLogger().lifecycle(":remapped project written to " + outputDir.toAbsolutePath()); } catch (IOException e) { throw new IllegalArgumentException("Error while loading mappings", e); @@ -157,8 +157,8 @@ public class MigrateMappingsTask extends AbstractLoomTask { return mappingTree; } - private static void migrateMappings(Project project, MinecraftMappedProvider minecraftMappedProvider, - Path inputDir, Path outputDir, MemoryMappingTree currentMappings, MemoryMappingTree targetMappings + private static void migrateMappings(Project project, LoomGradleExtension extension, MinecraftMappedProvider minecraftMappedProvider, + Path inputDir, Path outputDir, MemoryMappingTree currentMappings, MemoryMappingTree targetMappings ) throws IOException { project.getLogger().info(":joining mappings"); @@ -182,6 +182,16 @@ public class MigrateMappingsTask extends AbstractLoomTask { mercury.getClassPath().add(minecraftMappedProvider.getMappedJar().toPath()); mercury.getClassPath().add(minecraftMappedProvider.getIntermediaryJar().toPath()); + if (extension.isForge()) { + mercury.getClassPath().add(minecraftMappedProvider.getSrgJar().toPath()); + + if (extension.isForgeAndNotOfficial()) { + mercury.getClassPath().add(minecraftMappedProvider.getForgeMappedJar().toPath()); + mercury.getClassPath().add(minecraftMappedProvider.getForgeIntermediaryJar().toPath()); + mercury.getClassPath().add(minecraftMappedProvider.getForgeSrgJar().toPath()); + } + } + mercury.getProcessors().add(MercuryRemapper.create(mappingSet)); try { diff --git a/src/main/java/net/fabricmc/loom/task/RemapJarTask.java b/src/main/java/net/fabricmc/loom/task/RemapJarTask.java index 600d6821..1ced8299 100644 --- a/src/main/java/net/fabricmc/loom/task/RemapJarTask.java +++ b/src/main/java/net/fabricmc/loom/task/RemapJarTask.java @@ -24,23 +24,37 @@ package net.fabricmc.loom.task; +import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.UncheckedIOException; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.FileSystem; import java.nio.file.Files; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.jar.Manifest; import com.google.common.base.Preconditions; +import dev.architectury.tinyremapper.IMappingProvider; +import dev.architectury.tinyremapper.TinyRemapper; +import dev.architectury.tinyremapper.TinyUtils; +import dev.architectury.tinyremapper.extension.mixin.MixinExtension; +import org.cadixdev.at.AccessTransformSet; +import org.cadixdev.at.io.AccessTransformFormats; +import org.cadixdev.lorenz.MappingSet; import org.gradle.api.Action; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; @@ -48,6 +62,7 @@ import org.gradle.api.file.FileCollection; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.provider.Property; +import org.gradle.api.provider.SetProperty; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.TaskAction; @@ -55,9 +70,9 @@ import org.gradle.jvm.tasks.Jar; import org.jetbrains.annotations.ApiStatus; import net.fabricmc.loom.LoomGradleExtension; -import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.build.JarRemapper; import net.fabricmc.loom.build.MixinRefmapHelper; +import net.fabricmc.loom.build.nesting.EmptyNestedJarProvider; import net.fabricmc.loom.build.nesting.JarNester; import net.fabricmc.loom.build.nesting.MergedNestedJarProvider; import net.fabricmc.loom.build.nesting.NestedDependencyProvider; @@ -68,13 +83,16 @@ import net.fabricmc.loom.configuration.accesswidener.AccessWidenerFile; import net.fabricmc.loom.configuration.accesswidener.AccessWidenerJarProcessor; import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.FileSystemUtil; +import net.fabricmc.loom.util.LfWriter; +import net.fabricmc.loom.util.SourceRemapper; import net.fabricmc.loom.util.TinyRemapperHelper; import net.fabricmc.loom.util.ZipReprocessorUtil; import net.fabricmc.loom.util.ZipUtils; +import net.fabricmc.loom.util.aw2at.Aw2At; +import net.fabricmc.lorenztiny.TinyMappingsReader; +import net.fabricmc.mappingio.tree.MappingTree; import net.fabricmc.stitch.util.Pair; -import net.fabricmc.tinyremapper.TinyRemapper; -import net.fabricmc.tinyremapper.TinyUtils; -import net.fabricmc.tinyremapper.extension.mixin.MixinExtension; public class RemapJarTask extends Jar { private static final String MANIFEST_PATH = "META-INF/MANIFEST.MF"; @@ -84,6 +102,9 @@ public class RemapJarTask extends Jar { private final Property<Boolean> addDefaultNestedDependencies; private final Property<Boolean> remapAccessWidener; private final List<Action<TinyRemapper.Builder>> remapOptions = new ArrayList<>(); + private final Property<String> fromM; + private final Property<String> toM; + private final SetProperty<String> atAccessWideners; public JarRemapper jarRemapper; private FileCollection classpath; private final Set<Object> nestedPaths = new LinkedHashSet<>(); @@ -98,6 +119,12 @@ public class RemapJarTask extends Jar { .convention(true); remapAccessWidener = getProject().getObjects().property(Boolean.class) .convention(false); + fromM = getProject().getObjects().property(String.class) + .convention("named"); + toM = getProject().getObjects().property(String.class) + .convention(SourceRemapper.intermediary(getProject())); + atAccessWideners = getProject().getObjects().setProperty(String.class) + .empty(); if (!extension.getMixin().getUseLegacyMixinAp().get()) { remapOptions.add(b -> b.extension(new MixinExtension())); @@ -116,8 +143,10 @@ public class RemapJarTask extends Jar { scheduleRemap(singleRemap || LoomGradleExtension.get(getProject()).isRootProject()); if (singleRemap) { - jarRemapper.remap(); + jarRemapper.remap(getProject()); } + + convertAwToAt(); } public void scheduleRemap(boolean isMainRemapTask) throws Throwable { @@ -132,24 +161,27 @@ public class RemapJarTask extends Jar { MappingsProviderImpl mappingsProvider = extension.getMappingsProvider(); - String fromM = MappingsNamespace.NAMED.toString(); - String toM = MappingsNamespace.INTERMEDIARY.toString(); + String fromM = this.fromM.get(); + String toM = this.toM.get(); if (isMainRemapTask) { jarRemapper.addToClasspath(getRemapClasspath()); - jarRemapper.addMappings(TinyRemapperHelper.create(mappingsProvider.getMappings(), fromM, toM, false)); + jarRemapper.addMappings(TinyRemapperHelper.create((fromM.equals("srg") || toM.equals("srg")) && extension.shouldGenerateSrgTiny() ? mappingsProvider.getMappingsWithSrg() : mappingsProvider.getMappings(), fromM, toM, false)); } for (File mixinMapFile : extension.getAllMixinMappings()) { if (mixinMapFile.exists()) { - jarRemapper.addMappings(TinyUtils.createTinyMappingProvider(mixinMapFile.toPath(), fromM, toM)); + IMappingProvider provider = TinyUtils.createTinyMappingProvider(mixinMapFile.toPath(), fromM, "intermediary"); + jarRemapper.addMappings(!toM.equals("intermediary") ? remapToSrg(extension, provider, "intermediary", toM) : provider); } } // Add remap options to the jar remapper jarRemapper.addOptions(this.remapOptions); + project.getLogger().info(":scheduling remap " + input.getFileName() + " from " + fromM + " to " + toM); + NestedJarProvider nestedJarProvider = getNestedJarProvider(); nestedJarProvider.prepare(getProject()); @@ -166,7 +198,7 @@ public class RemapJarTask extends Jar { } AccessWidenerFile awFile = AccessWidenerFile.fromModJar(remapData.input); - Preconditions.checkNotNull(awFile, "Failed to find accessWidener in fabric.mod.json: " + remapData.input); + Preconditions.checkNotNull(awFile, "Failed to find accessWidener in fabric.mod.json / architectury.common.json: " + remapData.input); return Pair.of(awFile.name(), data); } @@ -182,6 +214,8 @@ public class RemapJarTask extends Jar { if (MixinRefmapHelper.addRefmapName(project, output)) { project.getLogger().debug("Transformed mixin reference maps in output JAR!"); } + } else if (extension.isForge()) { + throw new RuntimeException("Forge must have useLegacyMixinAp enabled"); } if (getAddNestedDependencies().getOrElse(false)) { @@ -196,23 +230,25 @@ public class RemapJarTask extends Jar { } } - // Add data to the manifest - try { - int count = ZipUtils.transform(data.output, Map.of(MANIFEST_PATH, bytes -> { - var manifest = new Manifest(new ByteArrayInputStream(bytes)); - var manifestConfiguration = new JarManifestConfiguration(project); + if (!extension.isForge()) { + // Add data to the manifest + try { + int count = ZipUtils.transform(data.output, Map.of(MANIFEST_PATH, bytes -> { + var manifest = new Manifest(new ByteArrayInputStream(bytes)); + var manifestConfiguration = new JarManifestConfiguration(project); - manifestConfiguration.configure(manifest); - manifest.getMainAttributes().putValue("Fabric-Mapping-Namespace", toM); + manifestConfiguration.configure(manifest); + manifest.getMainAttributes().putValue("Fabric-Mapping-Namespace", toM); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - manifest.write(out); - return out.toByteArray(); - })); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + manifest.write(out); + return out.toByteArray(); + })); - Preconditions.checkState(count > 0, "Did not transform any jar manifest"); - } catch (IOException e) { - throw new UncheckedIOException("Failed to transform jar manifest", e); + Preconditions.checkState(count > 0, "Did not transform any jar manifest"); + } catch (IOException e) { + throw new UncheckedIOException("Failed to transform jar manifest", e); + } } if (isReproducibleFileOrder() || !isPreserveFileTimestamps()) { @@ -226,6 +262,10 @@ public class RemapJarTask extends Jar { } private NestedJarProvider getNestedJarProvider() { + if (!LoomGradleExtension.get(getProject()).supportsInclude()) { + return EmptyNestedJarProvider.INSTANCE; + } + Configuration includeConfiguration = getProject().getConfigurations().getByName(Constants.Configurations.INCLUDE); if (!addDefaultNestedDependencies.getOrElse(true)) { @@ -244,6 +284,57 @@ public class RemapJarTask extends Jar { ); } + private IMappingProvider remapToSrg(LoomGradleExtension extension, IMappingProvider parent, String from, String to) throws IOException { + MappingTree mappings = (from.equals("srg") || to.equals("srg")) && extension.shouldGenerateSrgTiny() ? extension.getMappingsProvider().getMappingsWithSrg() : extension.getMappingsProvider().getMappings(); + + return sink -> { + parent.load(new IMappingProvider.MappingAcceptor() { + @Override + public void acceptClass(String srcName, String dstName) { + String srgName = mappings.getClasses() + .stream() + .filter(it -> Objects.equals(it.getName(from), dstName)) + .findFirst() + .map(it -> it.getName(to)) + .orElse(dstName); + sink.acceptClass(srcName, srgName); + } + + @Override + public void acceptMethod(IMappingProvider.Member method, String dstName) { + String srgName = mappings.getClasses() + .stream() + .flatMap(it -> it.getMethods().stream()) + .filter(it -> Objects.equals(it.getName(from), dstName)) + .findFirst() + .map(it -> it.getName(to)) + .orElse(dstName); + sink.acceptMethod(method, srgName); + } + + @Override + public void acceptField(IMappingProvider.Member field, String dstName) { + String srgName = mappings.getClasses() + .stream() + .flatMap(it -> it.getFields().stream()) + .filter(it -> Objects.equals(it.getName(from), dstName)) + .findFirst() + .map(it -> it.getName(to)) + .orElse(dstName); + sink.acceptField(field, srgName); + } + + @Override + public void acceptMethodArg(IMappingProvider.Member method, int lvIndex, String dstName) { + } + + @Override + public void acceptMethodVar(IMappingProvider.Member method, int lvIndex, int startOpIdx, int asmIndex, String dstName) { + } + }); + }; + } + private Path[] getRemapClasspath() { FileCollection files = this.classpath; @@ -257,6 +348,56 @@ public class RemapJarTask extends Jar { .toArray(Path[]::new); } + private void convertAwToAt() throws IOException { + if (!this.atAccessWideners.isPresent()) { + return; + } + + Set<String> atAccessWideners = this.atAccessWideners.get(); + + if (atAccessWideners.isEmpty()) { + return; + } + + AccessTransformSet at = AccessTransformSet.create(); + File jar = getArchiveFile().get().getAsFile(); + + try (FileSystemUtil.Delegate fileSystem = FileSystemUtil.getJarFileSystem(jar, false)) { + FileSystem fs = fileSystem.get(); + Path atPath = fs.getPath(Constants.Forge.ACCESS_TRANSFORMER_PATH); + + if (Files.exists(atPath)) { + throw new FileAlreadyExistsException("Jar " + jar + " already contains an access transformer - cannot convert AWs!"); + } + + for (String aw : atAccessWideners) { + Path awPath = fs.getPath(aw); + + if (Files.notExists(awPath)) { + throw new NoSuchFileException("Could not find AW '" + aw + "' to convert into AT!"); + } + + try (BufferedReader reader = Files.newBufferedReader(awPath, StandardCharsets.UTF_8)) { + at.merge(Aw2At.toAccessTransformSet(reader)); + } + + Files.delete(awPath); + } + + LoomGradleExtension extension = LoomGradleExtension.get(getProject()); + MappingTree mappings = (fromM.get().equals("srg") || toM.get().equals("srg")) && extension.shouldGenerateSrgTiny() ? extension.getMappingsProvider().getMappingsWithSrg() : extension.getMappingsProvider().getMappings(); + + try (TinyMappingsReader reader = new TinyMappingsReader(mappings, fromM.get(), toM.get())) { + MappingSet mappingSet = reader.read(); + at = at.remap(mappingSet); + } + + try (Writer writer = new LfWriter(Files.newBufferedWriter(atPath))) { + AccessTransformFormats.FML.write(writer, at); + } + } + } + @InputFile public RegularFileProperty getInput() { return input; @@ -277,6 +418,19 @@ public class RemapJarTask extends Jar { return remapAccessWidener; } + /** + * Gets the jar paths to the access wideners that will be converted to ATs for Forge runtime. + * If you specify multiple files, they will be merged into one. + * + * <p>The specified files will be converted and removed from the final jar. + * + * @return the property containing access widener paths in the final jar + */ + @Input + public SetProperty<String> getAtAccessWideners() { + return atAccessWideners; + } + public void remapOptions(Action<TinyRemapper.Builder> action) { this.remapOptions.add(action); } @@ -299,4 +453,14 @@ public class RemapJarTask extends Jar { return this; } + + @Input + public Property<String> getFromM() { + return fromM; + } + + @Input + public Property<String> getToM() { + return toM; + } } diff --git a/src/main/java/net/fabricmc/loom/task/RemapSourcesJarTask.java b/src/main/java/net/fabricmc/loom/task/RemapSourcesJarTask.java index 26fd6a37..97556c13 100644 --- a/src/main/java/net/fabricmc/loom/task/RemapSourcesJarTask.java +++ b/src/main/java/net/fabricmc/loom/task/RemapSourcesJarTask.java @@ -38,19 +38,26 @@ import net.fabricmc.loom.util.SourceRemapper; public class RemapSourcesJarTask extends AbstractLoomTask { private final RegularFileProperty input = getProject().getObjects().fileProperty(); private final RegularFileProperty output = getProject().getObjects().fileProperty().convention(input); - private final Property<String> targetNamespace = getProject().getObjects().property(String.class).convention(MappingsNamespace.INTERMEDIARY.toString()); + private final Property<String> sourceNamespace; + private final Property<String> targetNamespace; private SourceRemapper sourceRemapper = null; private final Property<Boolean> preserveFileTimestamps = getProject().getObjects().property(Boolean.class).convention(true); private final Property<Boolean> reproducibleFileOrder = getProject().getObjects().property(Boolean.class).convention(false); public RemapSourcesJarTask() { + this.sourceNamespace = getProject().getObjects().property(String.class).convention("named"); + this.targetNamespace = getProject().getObjects().property(String.class).convention(SourceRemapper.intermediary(getProject())); } @TaskAction public void remap() throws Exception { if (sourceRemapper == null) { - String direction = targetNamespace.get(); - SourceRemapper.remapSources(getProject(), input.get().getAsFile(), output.get().getAsFile(), direction.equals(MappingsNamespace.NAMED.toString()), reproducibleFileOrder.get(), preserveFileTimestamps.get()); + if (sourceNamespace.get().equals(targetNamespace.get())) { + SourceRemapper.remapSources(getProject(), getInput().get().getAsFile(), getOutput().get().getAsFile(), + targetNamespace.get().equals(MappingsNamespace.NAMED.toString()) ? SourceRemapper.intermediary(getProject()) : "named", targetNamespace.get(), reproducibleFileOrder.get(), preserveFileTimestamps.get()); + } else { + SourceRemapper.remapSources(getProject(), getInput().get().getAsFile(), getOutput().get().getAsFile(), sourceNamespace.get(), targetNamespace.get(), reproducibleFileOrder.get(), preserveFileTimestamps.get()); + } } else { sourceRemapper.scheduleRemapSources(input.get().getAsFile(), output.get().getAsFile(), reproducibleFileOrder.get(), preserveFileTimestamps.get()); } @@ -77,6 +84,11 @@ public class RemapSourcesJarTask extends AbstractLoomTask { } @Input + public Property<String> getSourceNamespace() { + return sourceNamespace; + } + + @Input public Property<String> getTargetNamespace() { return targetNamespace; } diff --git a/src/main/java/net/fabricmc/loom/task/RunDataTask.java b/src/main/java/net/fabricmc/loom/task/RunDataTask.java new file mode 100644 index 00000000..37b3430b --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/RunDataTask.java @@ -0,0 +1,38 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.task; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.configuration.ide.RunConfig; + +@Deprecated // Replaced by RunGameTask +public class RunDataTask extends AbstractRunTask { + public RunDataTask() { + super(project -> { + LoomGradleExtension extension = LoomGradleExtension.get(project); + return RunConfig.runConfig(project, extension.getRunConfigs().getByName("data")); + }); + } +} diff --git a/src/main/java/net/fabricmc/loom/task/ValidateAccessWidenerTask.java b/src/main/java/net/fabricmc/loom/task/ValidateAccessWidenerTask.java index 51f9d97c..7fc9d227 100644 --- a/src/main/java/net/fabricmc/loom/task/ValidateAccessWidenerTask.java +++ b/src/main/java/net/fabricmc/loom/task/ValidateAccessWidenerTask.java @@ -32,6 +32,8 @@ import java.nio.file.Files; import javax.inject.Inject; +import dev.architectury.tinyremapper.TinyRemapper; +import dev.architectury.tinyremapper.api.TrEnvironment; import org.gradle.api.DefaultTask; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.tasks.InputFile; @@ -42,8 +44,6 @@ import net.fabricmc.accesswidener.AccessWidenerFormatException; import net.fabricmc.accesswidener.AccessWidenerReader; import net.fabricmc.accesswidener.AccessWidenerVisitor; import net.fabricmc.loom.LoomGradleExtension; -import net.fabricmc.tinyremapper.TinyRemapper; -import net.fabricmc.tinyremapper.api.TrEnvironment; public abstract class ValidateAccessWidenerTask extends DefaultTask { @SkipWhenEmpty diff --git a/src/main/java/net/fabricmc/loom/util/Checksum.java b/src/main/java/net/fabricmc/loom/util/Checksum.java index e3607d4c..ba9cd2e6 100644 --- a/src/main/java/net/fabricmc/loom/util/Checksum.java +++ b/src/main/java/net/fabricmc/loom/util/Checksum.java @@ -27,6 +27,7 @@ package net.fabricmc.loom.util; import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; import com.google.common.hash.HashCode; import com.google.common.hash.Hashing; @@ -44,8 +45,9 @@ public class Checksum { try { HashCode hash = Files.asByteSource(file).hash(Hashing.sha1()); - log.debug("Checksum check: '" + hash.toString() + "' == '" + checksum + "'?"); - return hash.toString().equals(checksum); + String hashString = hash.toString(); + log.debug("Checksum check: '" + hashString + "' == '" + checksum + "'?"); + return hashString.equals(checksum); } catch (IOException e) { e.printStackTrace(); } @@ -70,4 +72,9 @@ public class Checksum { throw new UncheckedIOException("Failed to get file hash of " + file, e); } } + + public static byte[] sha256(String string) { + HashCode hash = Hashing.sha256().hashString(string, StandardCharsets.UTF_8); + return hash.asBytes(); + } } diff --git a/src/main/java/net/fabricmc/loom/util/Constants.java b/src/main/java/net/fabricmc/loom/util/Constants.java index dae98a87..8a68b0fb 100644 --- a/src/main/java/net/fabricmc/loom/util/Constants.java +++ b/src/main/java/net/fabricmc/loom/util/Constants.java @@ -34,6 +34,7 @@ import net.fabricmc.loom.configuration.RemappedConfigurationEntry; import net.fabricmc.loom.configuration.RemappedConfigurationEntry.PublishingMode; public class Constants { + public static final String PLUGIN_ID = "dev.architectury.loom"; public static final String LIBRARIES_BASE = "https://libraries.minecraft.net/"; public static final String RESOURCES_BASE = "https://resources.download.minecraft.net/"; public static final String VERSION_MANIFESTS = "https://launchermeta.mojang.com/mc/game/version_manifest_v2.json"; @@ -72,6 +73,30 @@ public class Constants { public static final String MAPPINGS_FINAL = "mappingsFinal"; public static final String LOADER_DEPENDENCIES = "loaderLibraries"; public static final String LOOM_DEVELOPMENT_DEPENDENCIES = "loomDevelopmentDependencies"; + public static final String SRG = "srg"; + public static final String MCP_CONFIG = "mcp"; + public static final String FORGE = "forge"; + public static final String FORGE_USERDEV = "forgeUserdev"; + public static final String FORGE_INSTALLER = "forgeInstaller"; + public static final String FORGE_UNIVERSAL = "forgeUniversal"; + /** + * Forge's own dependencies. Not intended to be used by users, + * {@link #FORGE_RUNTIME_LIBRARY forgeRuntimeLibrary} is for that instead. + */ + public static final String FORGE_DEPENDENCIES = "forgeDependencies"; + public static final String FORGE_NAMED = "forgeNamed"; + /** + * "Extra" runtime dependencies on Forge. Contains the Minecraft resources + * and {@linkplain Dependencies#FORGE_RUNTIME the Architectury Loom runtime}. + */ + public static final String FORGE_EXTRA = "forgeExtra"; + /** + * The configuration used to create the Forge runtime classpath file list. + * Users can also directly add files to this config. + * + * @see net.fabricmc.loom.configuration.providers.forge.ForgeUserdevProvider + */ + public static final String FORGE_RUNTIME_LIBRARY = "forgeRuntimeLibrary"; public static final String MAPPING_CONSTANTS = "mappingsConstants"; public static final String UNPICK_CLASSPATH = "unpick"; /** @@ -94,6 +119,9 @@ public class Constants { public static final String DEV_LAUNCH_INJECTOR = "net.fabricmc:dev-launch-injector:"; public static final String TERMINAL_CONSOLE_APPENDER = "net.minecrell:terminalconsoleappender:"; public static final String JETBRAINS_ANNOTATIONS = "org.jetbrains:annotations:"; + public static final String JAVAX_ANNOTATIONS = "com.google.code.findbugs:jsr305:"; // I hate that I have to add these. + public static final String FORGE_RUNTIME = "dev.architectury:architectury-loom-runtime:"; + public static final String ACCESS_TRANSFORMERS = "net.minecraftforge:accesstransformers:"; private Dependencies() { } @@ -106,6 +134,10 @@ public class Constants { public static final String DEV_LAUNCH_INJECTOR = "0.2.1+build.8"; public static final String TERMINAL_CONSOLE_APPENDER = "1.2.0"; public static final String JETBRAINS_ANNOTATIONS = "22.0.0"; + public static final String JAVAX_ANNOTATIONS = "3.0.2"; + public static final String FORGE_RUNTIME = "1.1.3"; + public static final String ACCESS_TRANSFORMERS = "3.0.1"; + public static final String ACCESS_TRANSFORMERS_NEW = "8.0.5"; private Versions() { } @@ -131,10 +163,18 @@ public class Constants { } public static final class TaskGroup { - public static final String FABRIC = "fabric"; + public static final String FABRIC = "loom"; public static final String IDE = "ide"; private TaskGroup() { } } + + public static final class Forge { + public static final String LAUNCH_TESTING = "net.minecraftforge.userdev.LaunchTesting"; + public static final String ACCESS_TRANSFORMER_PATH = "META-INF/accesstransformer.cfg"; + + private Forge() { + } + } } diff --git a/src/main/java/net/fabricmc/loom/util/DependencyDownloader.java b/src/main/java/net/fabricmc/loom/util/DependencyDownloader.java new file mode 100644 index 00000000..329d88bf --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/DependencyDownloader.java @@ -0,0 +1,97 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.util; + +import java.io.File; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.Dependency; +import org.gradle.api.artifacts.ModuleDependency; +import org.gradle.api.file.FileCollection; + +/** + * Simplified dependency downloading. + * + * @author Juuz + */ +public final class DependencyDownloader { + /** + * Resolves a dependency as well as its transitive dependencies into a {@link FileCollection}. + * + * @param project the project needing these files + * @param dependencyNotation the dependency notation + * @return the resolved files + */ + public static FileCollection download(Project project, String dependencyNotation) { + return download(project, dependencyNotation, true, false); + } + + public static FileCollection download(Project project, String dependencyNotation, boolean transitive, boolean resolve) { + Dependency dependency = project.getDependencies().create(dependencyNotation); + + if (dependency instanceof ModuleDependency) { + ((ModuleDependency) dependency).setTransitive(transitive); + } + + Configuration config = project.getConfigurations().detachedConfiguration(dependency); + config.setTransitive(transitive); + FileCollection files = config.fileCollection(dep -> true); + + if (resolve) { + files = project.files(files.getFiles()); + } + + return files; + } + + private static Set<File> resolve(Configuration configuration, boolean transitive) { + Configuration copy = configuration.copy(); + copy.setTransitive(transitive); + Set<File> files = new LinkedHashSet<>(copy.resolve()); + + for (Configuration extendsForm : configuration.getExtendsFrom()) { + files.addAll(resolve(extendsForm, transitive)); + } + + return files; + } + + /** + * Resolves a configuration and its superconfigurations. + * + * <p>Note that unlike resolving a {@linkplain Configuration#copyRecursive() recursive copy} of the configuration, + * this method overrides the transitivity of all superconfigurations as well. + * + * @param configuration the configuration to resolve + * @param transitive true if transitive dependencies should be included, false otherwise + * @return a mutable set containing the resolved files of the configuration + */ + public static Set<File> resolveFiles(Configuration configuration, boolean transitive) { + return resolve(configuration, transitive); + } +} diff --git a/src/main/java/net/fabricmc/loom/util/DownloadUtil.java b/src/main/java/net/fabricmc/loom/util/DownloadUtil.java index fcc79aa7..56ccf8d3 100644 --- a/src/main/java/net/fabricmc/loom/util/DownloadUtil.java +++ b/src/main/java/net/fabricmc/loom/util/DownloadUtil.java @@ -48,8 +48,8 @@ public class DownloadUtil { * @param logger The logger to print everything to, typically from {@link Project#getLogger()} * @throws IOException If an exception occurs during the process */ - public static void downloadIfChanged(URL from, File to, Logger logger) throws IOException { - downloadIfChanged(from, to, logger, false); + public static boolean downloadIfChanged(URL from, File to, Logger logger) throws IOException { + return downloadIfChanged(from, to, logger, false); } /** @@ -61,7 +61,7 @@ public class DownloadUtil { * @param quiet Whether to only print warnings (when <code>true</code>) or everything * @throws IOException If an exception occurs during the process */ - public static void downloadIfChanged(URL from, File to, Logger logger, boolean quiet) throws IOException { + public static boolean downloadIfChanged(URL from, File to, Logger logger, boolean quiet) throws IOException { HttpURLConnection connection = (HttpURLConnection) from.openConnection(); if (LoomGradlePlugin.refreshDeps) { @@ -102,7 +102,7 @@ public class DownloadUtil { logger.info("'{}' Not Modified, skipping.", to); } - return; //What we've got is already fine + return false; //What we've got is already fine } long contentLength = connection.getContentLengthLong(); @@ -140,6 +140,8 @@ public class DownloadUtil { saveETag(to, eTag, logger); } + + return true; } /** @@ -228,5 +230,7 @@ public class DownloadUtil { if (etagFile.exists()) { etagFile.delete(); } + + HashedDownloadUtil.delete(file); } } diff --git a/src/main/java/net/fabricmc/loom/util/FileSystemUtil.java b/src/main/java/net/fabricmc/loom/util/FileSystemUtil.java index 377bb8cb..05f0642f 100644 --- a/src/main/java/net/fabricmc/loom/util/FileSystemUtil.java +++ b/src/main/java/net/fabricmc/loom/util/FileSystemUtil.java @@ -78,6 +78,8 @@ public final class FileSystemUtil { return new Delegate(FileSystems.newFileSystem(jarUri, create ? jfsArgsCreate : jfsArgsEmpty), true); } catch (FileSystemAlreadyExistsException e) { return new Delegate(FileSystems.getFileSystem(jarUri), false); + } catch (IOException e) { + throw new IOException("Could not create JAR file system for " + uri + " (create: " + create + ")", e); } } } diff --git a/src/main/java/net/fabricmc/loom/util/HashedDownloadUtil.java b/src/main/java/net/fabricmc/loom/util/HashedDownloadUtil.java index 3ca404e2..a9b77ab4 100644 --- a/src/main/java/net/fabricmc/loom/util/HashedDownloadUtil.java +++ b/src/main/java/net/fabricmc/loom/util/HashedDownloadUtil.java @@ -43,20 +43,48 @@ import org.gradle.api.logging.Logger; import net.fabricmc.loom.LoomGradlePlugin; public class HashedDownloadUtil { + public static boolean requiresDownload(File to, String expectedHash, Logger logger) { + if (LoomGradlePlugin.refreshDeps) { + return true; + } + + if (to.exists()) { + String sha1 = getSha1(to, logger); + + // The hash in the sha1 file matches + return !expectedHash.equals(sha1); + } + + return true; + } + public static void downloadIfInvalid(URL from, File to, String expectedHash, Logger logger, boolean quiet) throws IOException { - downloadIfInvalid(from, to, expectedHash, logger, quiet, () -> { }); + downloadIfInvalid(from, to, expectedHash, logger, quiet, true); + } + + public static void downloadIfInvalid(URL from, File to, String expectedHash, Logger logger, boolean quiet, boolean strict) throws IOException { + downloadIfInvalid(from, to, expectedHash, logger, quiet, strict, () -> { }); } - public static void downloadIfInvalid(URL from, File to, String expectedHash, Logger logger, boolean quiet, Runnable startDownload) throws IOException { + public static void downloadIfInvalid(URL from, File to, String expectedHash, Logger logger, boolean quiet, boolean strict, Runnable startDownload) throws IOException { if (LoomGradlePlugin.refreshDeps) { delete(to); } - String sha1 = getSha1(to, logger); - - if (expectedHash.equals(sha1)) { - // The hash in the sha1 file matches - return; + if (to.exists()) { + if (strict) { + if (Checksum.equals(to, expectedHash)) { + // The hash matches the target file + return; + } + } else { + String sha1 = getSha1(to, logger); + + if (expectedHash.equals(sha1)) { + // The hash in the sha1 file matches + return; + } + } } startDownload.run(); diff --git a/src/main/java/net/fabricmc/loom/util/LfWriter.java b/src/main/java/net/fabricmc/loom/util/LfWriter.java new file mode 100644 index 00000000..cbd6540f --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/LfWriter.java @@ -0,0 +1,43 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.util; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.Writer; + +/** + * A {@link BufferedWriter} that writes {@code \n} (LF) instead of {@link System#lineSeparator()}. + */ +public class LfWriter extends BufferedWriter { + public LfWriter(Writer out) { + super(out); + } + + @Override + public void newLine() throws IOException { + write('\n'); + } +} diff --git a/src/main/java/net/fabricmc/loom/util/LoggerFilter.java b/src/main/java/net/fabricmc/loom/util/LoggerFilter.java new file mode 100644 index 00000000..1f9f9b96 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/LoggerFilter.java @@ -0,0 +1,49 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.util; + +import java.io.PrintStream; + +import org.jetbrains.annotations.NotNull; + +public class LoggerFilter { + public static void replaceSystemOut() { + try { + PrintStream previous = System.out; + System.setOut(new PrintStream(previous) { + @Override + public PrintStream printf(@NotNull String format, Object... args) { + if (format.equals("unknown invokedynamic bsm: %s/%s%s (tag=%d iif=%b)%n")) { + return this; + } + + return super.printf(format, args); + } + }); + } catch (SecurityException ignored) { + // Failed to replace logger filter, just ignore + } + } +} diff --git a/src/main/java/net/fabricmc/loom/util/MappingsProviderVerbose.java b/src/main/java/net/fabricmc/loom/util/MappingsProviderVerbose.java new file mode 100644 index 00000000..9ddb07ef --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/MappingsProviderVerbose.java @@ -0,0 +1,115 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.util; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.UncheckedIOException; +import java.lang.reflect.Field; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Set; + +import dev.architectury.tinyremapper.IMappingProvider; +import dev.architectury.tinyremapper.TinyRemapper; + +import net.fabricmc.mappingio.adapter.RegularAsFlatMappingVisitor; +import net.fabricmc.mappingio.format.Tiny2Writer; +import net.fabricmc.mappingio.tree.MemoryMappingTree; + +public class MappingsProviderVerbose { + public static void saveFile(TinyRemapper providers) throws IOException { + try { + Field field = TinyRemapper.class.getDeclaredField("mappingProviders"); + field.setAccessible(true); + Set<IMappingProvider> mappingProviders = (Set<IMappingProvider>) field.get(providers); + saveFile(mappingProviders); + } catch (IllegalAccessException | NoSuchFieldException e) { + e.printStackTrace(); + } + } + + public static void saveFile(Iterable<IMappingProvider> providers) throws IOException { + MemoryMappingTree tree = new MemoryMappingTree(); + tree.setSrcNamespace("from"); + tree.setDstNamespaces(new ArrayList<>(Collections.singletonList("to"))); + RegularAsFlatMappingVisitor flatVisitor = new RegularAsFlatMappingVisitor(tree); + + for (IMappingProvider provider : providers) { + provider.load(new IMappingProvider.MappingAcceptor() { + @Override + public void acceptClass(String from, String to) { + try { + flatVisitor.visitClass(from, to); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public void acceptMethod(IMappingProvider.Member from, String to) { + try { + flatVisitor.visitMethod(from.owner, from.name, from.desc, to); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public void acceptMethodArg(IMappingProvider.Member from, int lvIndex, String to) { + try { + flatVisitor.visitMethodArg(from.owner, from.name, from.desc, lvIndex, lvIndex, "", to); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public void acceptMethodVar(IMappingProvider.Member from, int i, int i1, int i2, String s) { + // NO-OP + } + + @Override + public void acceptField(IMappingProvider.Member from, String to) { + try { + flatVisitor.visitField(from.owner, from.name, from.desc, to); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + }); + } + + Path check = Files.createTempFile("CHECK", null); + StringWriter stringWriter = new StringWriter(); + Tiny2Writer tiny2Writer = new Tiny2Writer(stringWriter, false); + tree.accept(tiny2Writer); + Files.writeString(check, stringWriter.toString(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + System.out.println("Saved debug check mappings to " + check); + } +} diff --git a/src/main/java/net/fabricmc/loom/util/ModPlatform.java b/src/main/java/net/fabricmc/loom/util/ModPlatform.java new file mode 100644 index 00000000..0f7ce897 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/ModPlatform.java @@ -0,0 +1,50 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.util; + +import java.util.Locale; + +import org.gradle.api.GradleException; +import org.gradle.api.Project; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.api.LoomGradleExtensionAPI; + +public enum ModPlatform { + FABRIC, + FORGE; + + public static void assertPlatform(Project project, ModPlatform platform) { + assertPlatform(LoomGradleExtension.get(project), platform); + } + + public static void assertPlatform(LoomGradleExtensionAPI extension, ModPlatform platform) { + if (extension.getPlatform().get() != platform) { + String msg = "Loom is not running on %s.%nYou can switch to it by adding 'loom.platform = %s' to your gradle.properties"; + String name = platform.name().toLowerCase(Locale.ROOT); + throw new GradleException(String.format(msg, name, name)); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/util/PropertyUtil.java b/src/main/java/net/fabricmc/loom/util/PropertyUtil.java new file mode 100644 index 00000000..98e85b68 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/PropertyUtil.java @@ -0,0 +1,45 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.util; + +import org.gradle.api.provider.HasConfigurableValue; +import org.gradle.api.provider.Provider; + +/** + * Working with properties. + */ +public final class PropertyUtil { + /** + * Gets a property and finalizes its value. + * + * @param property the property + * @param <T> the value type + * @return the current property value + */ + public static <T, P extends Provider<T> & HasConfigurableValue> T getAndFinalize(P property) { + property.finalizeValue(); + return property.get(); + } +} diff --git a/src/main/java/net/fabricmc/loom/util/SourceRemapper.java b/src/main/java/net/fabricmc/loom/util/SourceRemapper.java index f37211be..7873c78f 100644 --- a/src/main/java/net/fabricmc/loom/util/SourceRemapper.java +++ b/src/main/java/net/fabricmc/loom/util/SourceRemapper.java @@ -39,7 +39,6 @@ import org.cadixdev.mercury.remapper.MercuryRemapper; import org.gradle.api.Project; import net.fabricmc.loom.LoomGradleExtension; -import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.configuration.RemappedConfigurationEntry; import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; import net.fabricmc.loom.util.gradle.ProgressLoggerHelper; @@ -48,18 +47,29 @@ import net.fabricmc.mappingio.tree.MemoryMappingTree; public class SourceRemapper { private final Project project; - private final boolean toNamed; + private String from; + private String to; private final List<Consumer<ProgressLoggerHelper>> remapTasks = new ArrayList<>(); private Mercury mercury; - public SourceRemapper(Project project, boolean toNamed) { + public SourceRemapper(Project project, boolean named) { + this(project, named ? intermediary(project) : "named", !named ? intermediary(project) : "named"); + } + + public static String intermediary(Project project) { + LoomGradleExtension extension = LoomGradleExtension.get(project); + return extension.isForge() ? "srg" : "intermediary"; + } + + public SourceRemapper(Project project, String from, String to) { this.project = project; - this.toNamed = toNamed; + this.from = from; + this.to = to; } - public static void remapSources(Project project, File input, File output, boolean named, boolean reproducibleFileOrder, boolean preserveFileTimestamps) { - SourceRemapper sourceRemapper = new SourceRemapper(project, named); + public static void remapSources(Project project, File input, File output, String from, String to, boolean reproducibleFileOrder, boolean preserveFileTimestamps) { + SourceRemapper sourceRemapper = new SourceRemapper(project, from, to); sourceRemapper.scheduleRemapSources(input, output, reproducibleFileOrder, preserveFileTimestamps); sourceRemapper.remapAll(); } @@ -161,18 +171,27 @@ public class SourceRemapper { LoomGradleExtension extension = LoomGradleExtension.get(project); MappingsProviderImpl mappingsProvider = extension.getMappingsProvider(); - MappingSet mappings = extension.getOrCreateSrcMappingCache(toNamed ? 1 : 0, () -> { + String intermediary = extension.isForge() ? "srg" : "intermediary"; + int id = -1; + + if (from.equals(intermediary) && to.equals("named")) { + id = 1; + } else if (to.equals(intermediary) && from.equals("named")) { + id = 0; + } + + MappingSet mappings = extension.getOrCreateSrcMappingCache(id, () -> { try { - MemoryMappingTree m = mappingsProvider.getMappings(); - project.getLogger().info(":loading " + (toNamed ? "intermediary -> named" : "named -> intermediary") + " source mappings"); - return new TinyMappingsReader(m, toNamed ? MappingsNamespace.INTERMEDIARY.toString() : MappingsNamespace.NAMED.toString(), toNamed ? MappingsNamespace.NAMED.toString() : MappingsNamespace.INTERMEDIARY.toString()).read(); + MemoryMappingTree m = (from.equals("srg") || to.equals("srg")) && extension.shouldGenerateSrgTiny() ? mappingsProvider.getMappingsWithSrg() : mappingsProvider.getMappings(); + project.getLogger().info(":loading " + from + " -> " + to + " source mappings"); + return new TinyMappingsReader(m, from, to).read(); } catch (Exception e) { throw new RuntimeException(e); } }); - Mercury mercury = extension.getOrCreateSrcMercuryCache(toNamed ? 1 : 0, () -> { - Mercury m = createMercuryWithClassPath(project, toNamed); + Mercury mercury = extension.getOrCreateSrcMercuryCache(id, () -> { + Mercury m = createMercuryWithClassPath(project, to.equals("named")); for (File file : extension.getUnmappedModCollection()) { Path path = file.toPath(); @@ -185,6 +204,16 @@ public class SourceRemapper { m.getClassPath().add(extension.getMinecraftMappedProvider().getMappedJar().toPath()); m.getClassPath().add(extension.getMinecraftMappedProvider().getIntermediaryJar().toPath()); + if (extension.isForge()) { + m.getClassPath().add(extension.getMinecraftMappedProvider().getSrgJar().toPath()); + + if (extension.isForgeAndNotOfficial()) { + m.getClassPath().add(extension.getMinecraftMappedProvider().getForgeMappedJar().toPath()); + m.getClassPath().add(extension.getMinecraftMappedProvider().getForgeIntermediaryJar().toPath()); + m.getClassPath().add(extension.getMinecraftMappedProvider().getForgeSrgJar().toPath()); + } + } + Set<File> files = project.getConfigurations() .detachedConfiguration(project.getDependencies().create(Constants.Dependencies.JETBRAINS_ANNOTATIONS + Constants.Dependencies.Versions.JETBRAINS_ANNOTATIONS)) .resolve(); diff --git a/src/main/java/net/fabricmc/loom/util/ThreadingUtils.java b/src/main/java/net/fabricmc/loom/util/ThreadingUtils.java new file mode 100644 index 00000000..d6d06f79 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/ThreadingUtils.java @@ -0,0 +1,192 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.function.Function; +import java.util.stream.Collectors; + +import com.google.common.base.Stopwatch; + +public class ThreadingUtils { + public static <T> void run(T[] values, UnsafeConsumer<T> action) { + run(Arrays.stream(values) + .<UnsafeRunnable>map(t -> () -> action.accept(t)) + .collect(Collectors.toList())); + } + + public static <T> void run(Collection<T> values, UnsafeConsumer<T> action) { + run(values.stream() + .<UnsafeRunnable>map(t -> () -> action.accept(t)) + .collect(Collectors.toList())); + } + + public static void run(UnsafeRunnable... jobs) { + run(Arrays.asList(jobs)); + } + + public static void run(Collection<UnsafeRunnable> jobs) { + try { + ExecutorService service = Executors.newFixedThreadPool(Math.max(1, Math.min(jobs.size(), Runtime.getRuntime().availableProcessors()))); + List<Future<?>> futures = new LinkedList<>(); + + for (UnsafeRunnable runnable : jobs) { + futures.add(service.submit(() -> { + try { + runnable.run(); + } catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + })); + } + + for (Future<?> future : futures) { + future.get(); + } + + service.shutdownNow(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + + public static <T, R> List<R> get(Collection<T> values, Function<T, R> action) { + return get(values.stream() + .<UnsafeCallable<R>>map(t -> () -> action.apply(t)) + .collect(Collectors.toList())); + } + + @SafeVarargs + public static <T> List<T> get(UnsafeCallable<T>... jobs) { + return get(Arrays.asList(jobs)); + } + + public static <T> List<T> get(Collection<UnsafeCallable<T>> jobs) { + try { + ExecutorService service = Executors.newFixedThreadPool(Math.max(1, Math.min(jobs.size(), Runtime.getRuntime().availableProcessors()))); + List<Future<T>> futures = new LinkedList<>(); + List<T> result = new ArrayList<>(); + + for (UnsafeCallable<T> runnable : jobs) { + futures.add(service.submit(() -> { + try { + return runnable.call(); + } catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + })); + } + + for (Future<T> future : futures) { + result.add(future.get()); + } + + service.shutdownNow(); + return result; + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + + public interface UnsafeRunnable { + void run() throws Throwable; + } + + public interface UnsafeCallable<T> { + T call() throws Throwable; + } + + public interface UnsafeConsumer<T> { + void accept(T value) throws Throwable; + } + + public static TaskCompleter taskCompleter() { + return new TaskCompleter(); + } + + public static class TaskCompleter implements Function<Throwable, Void> { + Stopwatch stopwatch = Stopwatch.createUnstarted(); + List<CompletableFuture<?>> tasks = new ArrayList<>(); + ExecutorService service = Executors.newFixedThreadPool(Math.max(1, Runtime.getRuntime().availableProcessors())); + List<UnsafeConsumer<Stopwatch>> completionListener = new ArrayList<>(); + + public TaskCompleter add(UnsafeRunnable job) { + if (!stopwatch.isRunning()) { + stopwatch.start(); + } + + tasks.add(CompletableFuture.runAsync(() -> { + try { + job.run(); + } catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + }, service).exceptionally(this)); + + return this; + } + + public TaskCompleter onComplete(UnsafeConsumer<Stopwatch> consumer) { + completionListener.add(consumer); + return this; + } + + public void complete() { + try { + CompletableFuture.allOf(tasks.toArray(new CompletableFuture[0])).exceptionally(this).get(); + service.shutdownNow(); + + if (stopwatch.isRunning()) { + stopwatch.stop(); + } + } catch (Throwable e) { + throw new RuntimeException(e); + } finally { + try { + for (UnsafeConsumer<Stopwatch> consumer : completionListener) { + consumer.accept(stopwatch); + } + } catch (Throwable e) { + e.printStackTrace(); + } + } + } + + @Override + public Void apply(Throwable throwable) { + throwable.printStackTrace(); + return null; + } + } +} diff --git a/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java b/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java index d534f972..1dc42878 100644 --- a/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java +++ b/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java @@ -27,25 +27,31 @@ package net.fabricmc.loom.util; import java.io.File; import java.io.IOException; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.function.Consumer; import java.util.regex.Pattern; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import dev.architectury.tinyremapper.IMappingProvider; +import dev.architectury.tinyremapper.TinyRemapper; +import org.apache.commons.lang3.mutable.Mutable; +import org.apache.commons.lang3.mutable.MutableObject; +import org.apache.commons.lang3.tuple.Triple; import org.gradle.api.Project; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.mappingio.tree.MappingTree; import net.fabricmc.mappingio.tree.MemoryMappingTree; -import net.fabricmc.tinyremapper.IMappingProvider; -import net.fabricmc.tinyremapper.TinyRemapper; /** * Contains shortcuts to create tiny remappers using the mappings accessibly to the project. */ public final class TinyRemapperHelper { - private static final Map<String, String> JSR_TO_JETBRAINS = new ImmutableMap.Builder<String, String>() + public static final Map<String, String> JSR_TO_JETBRAINS = new ImmutableMap.Builder<String, String>() .put("javax/annotation/Nullable", "org/jetbrains/annotations/Nullable") .put("javax/annotation/Nonnull", "org/jetbrains/annotations/NotNull") .put("javax/annotation/concurrent/Immutable", "org/jetbrains/annotations/Unmodifiable") @@ -65,31 +71,62 @@ public final class TinyRemapperHelper { public static TinyRemapper getTinyRemapper(Project project, String fromM, String toM, boolean fixRecords, Consumer<TinyRemapper.Builder> builderConsumer) throws IOException { LoomGradleExtension extension = LoomGradleExtension.get(project); - MemoryMappingTree mappingTree = extension.getMappingsProvider().getMappings(); - if (fixRecords && !mappingTree.getSrcNamespace().equals(fromM)) { - throw new IllegalStateException("Mappings src namespace must match remap src namespace"); - } + TinyRemapper remapper = _getTinyRemapper(project, fixRecords, builderConsumer).getLeft(); + remapper.replaceMappings(ImmutableSet.of( + TinyRemapperHelper.create((fromM.equals("srg") || toM.equals("srg")) && extension.isForge() ? extension.getMappingsProvider().getMappingsWithSrg() : extension.getMappingsProvider().getMappings(), fromM, toM, true), + out -> TinyRemapperHelper.JSR_TO_JETBRAINS.forEach(out::acceptClass) + )); + return remapper; + } - int intermediaryNsId = mappingTree.getNamespaceId(MappingsNamespace.INTERMEDIARY.toString()); + public static Triple<TinyRemapper, Mutable<MemoryMappingTree>, List<TinyRemapper.ApplyVisitorProvider>> _getTinyRemapper(Project project, boolean fixRecords, Consumer<TinyRemapper.Builder> builderConsumer) throws IOException { + LoomGradleExtension extension = LoomGradleExtension.get(project); + Mutable<MemoryMappingTree> mappings = new MutableObject<>(); + List<TinyRemapper.ApplyVisitorProvider> postApply = new ArrayList<>(); TinyRemapper.Builder builder = TinyRemapper.newRemapper() - .withMappings(create(mappingTree, fromM, toM, true)) - .withMappings(out -> JSR_TO_JETBRAINS.forEach(out::acceptClass)) .renameInvalidLocals(true) - .rebuildSourceFilenames(true) - .invalidLvNamePattern(MC_LV_PATTERN) - .inferNameFromSameLvIndex(true) - .extraPreApplyVisitor((cls, next) -> { - if (fixRecords && !cls.isRecord() && "java/lang/Record".equals(cls.getSuperName())) { - return new RecordComponentFixVisitor(next, mappingTree, intermediaryNsId); - } + .logUnknownInvokeDynamic(false) + .ignoreConflicts(extension.isForge()) + .cacheMappings(true) + .threads(Runtime.getRuntime().availableProcessors()) + .logger(project.getLogger()::lifecycle) + .rebuildSourceFilenames(true); + + if (extension.isForge()) { + /* FORGE: Required for classes like aej$OptionalNamedTag (1.16.4) which are added by Forge patches. + * They won't get remapped to their proper packages, so IllegalAccessErrors will happen without ._. + */ + builder.fixPackageAccess(true); + } - return next; - }); + builder.invalidLvNamePattern(MC_LV_PATTERN); + builder.inferNameFromSameLvIndex(true); + builder.extraPreApplyVisitor((cls, next) -> { + if (fixRecords && !cls.isRecord() && "java/lang/Record".equals(cls.getSuperName()) && mappings.getValue() != null) { + return new RecordComponentFixVisitor(next, mappings.getValue(), mappings.getValue().getNamespaceId(MappingsNamespace.INTERMEDIARY.toString())); + } + + return next; + }); + builder.extraPostApplyVisitor((trClass, classVisitor) -> { + for (TinyRemapper.ApplyVisitorProvider provider : postApply) { + classVisitor = provider.insertApplyVisitor(trClass, classVisitor); + } + + return classVisitor; + }); builderConsumer.accept(builder); - return builder.build(); + return Triple.of(builder.build(), mappings, postApply); + } + + public static Triple<TinyRemapper, Mutable<MemoryMappingTree>, List<TinyRemapper.ApplyVisitorProvider>> getTinyRemapper(Project project, boolean fixRecords, Consumer<TinyRemapper.Builder> builderConsumer) throws IOException { + Triple<TinyRemapper, Mutable<MemoryMappingTree>, List<TinyRemapper.ApplyVisitorProvider>> remapper = _getTinyRemapper(project, fixRecords, builderConsumer); + remapper.getLeft().readClassPath(getMinecraftDependencies(project)); + remapper.getLeft().prepareClasses(); + return remapper; } public static Path[] getMinecraftDependencies(Project project) { diff --git a/src/main/java/net/fabricmc/loom/util/aw2at/Aw2At.java b/src/main/java/net/fabricmc/loom/util/aw2at/Aw2At.java new file mode 100644 index 00000000..06fd7e86 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/aw2at/Aw2At.java @@ -0,0 +1,118 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.util.aw2at; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; + +import org.cadixdev.at.AccessChange; +import org.cadixdev.at.AccessTransform; +import org.cadixdev.at.AccessTransformSet; +import org.cadixdev.at.ModifierChange; +import org.cadixdev.bombe.type.signature.MethodSignature; +import org.gradle.api.Project; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetContainer; + +import net.fabricmc.accesswidener.AccessWidenerReader; +import net.fabricmc.accesswidener.AccessWidenerVisitor; +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.task.RemapJarTask; + +/** + * Converts AW files to AT files. + * + * @author Juuz + */ +public final class Aw2At { + public static void setup(Project project, RemapJarTask remapJar) { + LoomGradleExtension extension = LoomGradleExtension.get(project); + + if (extension.getAccessWidenerPath().isPresent()) { + // Find the relative AW file name + String awName = null; + Path awPath = extension.getAccessWidenerPath().get().getAsFile().toPath(); + SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class); + SourceSet main = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME); + boolean found = false; + + for (File srcDir : main.getResources().getSrcDirs()) { + Path srcDirPath = srcDir.toPath().toAbsolutePath(); + + if (awPath.startsWith(srcDirPath)) { + awName = srcDirPath.relativize(awPath).toString().replace(File.separator, "/"); + found = true; + break; + } + } + + if (!found) { + awName = awPath.getFileName().toString(); + } + + remapJar.getAtAccessWideners().add(awName); + } + + remapJar.getAtAccessWideners().addAll(extension.getForge().getExtraAccessWideners()); + } + + public static AccessTransformSet toAccessTransformSet(BufferedReader reader) throws IOException { + AccessTransformSet atSet = AccessTransformSet.create(); + + new AccessWidenerReader(new AccessWidenerVisitor() { + @Override + public void visitClass(String name, AccessWidenerReader.AccessType access, boolean transitive) { + atSet.getOrCreateClass(name).merge(toAt(access)); + } + + @Override + public void visitMethod(String owner, String name, String descriptor, AccessWidenerReader.AccessType access, boolean transitive) { + atSet.getOrCreateClass(owner).mergeMethod(MethodSignature.of(name, descriptor), toAt(access)); + } + + @Override + public void visitField(String owner, String name, String descriptor, AccessWidenerReader.AccessType access, boolean transitive) { + atSet.getOrCreateClass(owner).mergeField(name, toAt(access)); + } + }).read(reader); + + return atSet; + } + + private static AccessTransform toAt(AccessWidenerReader.AccessType access) { + return switch (access) { + // FIXME: This behaviour doesn't match what the actual AW does for methods. + // - accessible makes the method final if it was private + // - extendable makes the method protected if it was (package-)private + // Neither of these can be achieved with Forge ATs without using bytecode analysis. + // However, this might be good enough for us. (The effects only apply in prod.) + case ACCESSIBLE -> AccessTransform.of(AccessChange.PUBLIC); + case EXTENDABLE -> AccessTransform.of(AccessChange.PUBLIC, ModifierChange.REMOVE); + case MUTABLE -> AccessTransform.of(ModifierChange.REMOVE); + }; + } +} diff --git a/src/main/java/net/fabricmc/loom/util/function/CollectionUtil.java b/src/main/java/net/fabricmc/loom/util/function/CollectionUtil.java new file mode 100644 index 00000000..389dbbef --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/function/CollectionUtil.java @@ -0,0 +1,75 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2020-2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.util.function; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * Stream-like utilities for working with collections. + * + * @author Juuz + */ +public final class CollectionUtil { + /** + * Finds the first element matching the predicate. + * + * @param collection the collection to be searched + * @param filter the predicate to be matched + * @param <E> the element type + * @return the first matching element, or empty if none match + */ + public static <E> Optional<E> find(Iterable<? extends E> collection, Predicate<? super E> filter) { + for (E e : collection) { + if (filter.test(e)) { + return Optional.of(e); + } + } + + return Optional.empty(); + } + + /** + * Transforms the collection with a function. + * + * @param collection the source collection + * @param transform the transformation function + * @param <A> the source type + * @param <B> the target type + * @return a mutable list with the transformed entries + */ + public static <A, B> List<B> map(Iterable<? extends A> collection, Function<? super A, ? extends B> transform) { + ArrayList<B> result = new ArrayList<>(); + + for (A a : collection) { + result.add(transform.apply(a)); + } + + return result; + } +} diff --git a/src/main/java/net/fabricmc/loom/util/function/FsPathConsumer.java b/src/main/java/net/fabricmc/loom/util/function/FsPathConsumer.java new file mode 100644 index 00000000..536721c7 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/function/FsPathConsumer.java @@ -0,0 +1,39 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2020-2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.util.function; + +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.Path; + +/** + * Consumes two file systems and corresponding path objects. + * + * @author Juuz + */ +@FunctionalInterface +public interface FsPathConsumer { + void accept(FileSystem sourceFs, FileSystem targetFs, Path sourcePath, Path targetPath) throws IOException; +} diff --git a/src/main/java/net/fabricmc/loom/util/function/IoConsumer.java b/src/main/java/net/fabricmc/loom/util/function/IoConsumer.java new file mode 100644 index 00000000..379d4293 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/function/IoConsumer.java @@ -0,0 +1,38 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2020-2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.util.function; + +import java.io.IOException; + +/** + * Like Consumer, but can throw IOException. + * + * @param <A> the result type + * @author Juuz + */ +@FunctionalInterface +public interface IoConsumer<A> { + void accept(A a) throws IOException; +} diff --git a/src/main/java/net/fabricmc/loom/util/function/LazyBool.java b/src/main/java/net/fabricmc/loom/util/function/LazyBool.java new file mode 100644 index 00000000..7d2933a4 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/function/LazyBool.java @@ -0,0 +1,52 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2020-2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.util.function; + +import java.util.Objects; +import java.util.function.BooleanSupplier; + +/** + * A lazily computed boolean value. + * + * @author Juuz + */ +public final class LazyBool implements BooleanSupplier { + private BooleanSupplier supplier; + private Boolean value; + + public LazyBool(BooleanSupplier supplier) { + this.supplier = Objects.requireNonNull(supplier, "supplier"); + } + + @Override + public boolean getAsBoolean() { + if (value == null) { + value = supplier.getAsBoolean(); + supplier = null; // Release the supplier + } + + return value; + } +} diff --git a/src/main/java/net/fabricmc/loom/util/srg/AtRemapper.java b/src/main/java/net/fabricmc/loom/util/srg/AtRemapper.java new file mode 100644 index 00000000..8e1d3008 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/srg/AtRemapper.java @@ -0,0 +1,140 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2020-2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.util.srg; + +import java.io.IOException; +import java.io.StringReader; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.List; +import java.util.function.UnaryOperator; + +import com.google.common.collect.ImmutableMap; +import org.gradle.api.logging.Logger; + +import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.function.CollectionUtil; +import net.fabricmc.mappingio.tree.MappingTree; + +/** + * Remaps AT classes from SRG to Yarn. + * + * @author Juuz + */ +public final class AtRemapper { + public static void remap(Logger logger, Path jar, MappingTree mappings) throws IOException { + try (FileSystem fs = FileSystems.newFileSystem(URI.create("jar:" + jar.toUri()), ImmutableMap.of("create", false))) { + Path atPath = fs.getPath(Constants.Forge.ACCESS_TRANSFORMER_PATH); + + if (Files.exists(atPath)) { + String atContent = Files.readString(atPath); + + String[] lines = atContent.split("\n"); + List<String> output = new ArrayList<>(lines.length); + + for (int i = 0; i < lines.length; i++) { + String line = lines[i].trim(); + + if (line.startsWith("#") || line.isBlank()) { + output.add(i, line); + continue; + } + + String[] parts = line.split("\\s+"); + + if (parts.length < 2) { + logger.warn("Invalid AT Line: " + line); + output.add(i, line); + continue; + } + + String name = parts[1].replace('.', '/'); + parts[1] = CollectionUtil.find( + mappings.getClasses(), + def -> def.getName("srg").equals(name) + ).map(def -> def.getName("named")).orElse(name).replace('/', '.'); + + if (parts.length >= 3) { + if (parts[2].contains("(")) { + parts[2] = parts[2].substring(0, parts[2].indexOf('(')) + remapDescriptor(parts[2].substring(parts[2].indexOf('(')), s -> { + return CollectionUtil.find( + mappings.getClasses(), + def -> def.getName("srg").equals(s) + ).map(def -> def.getName("named")).orElse(s); + }); + } + } + + output.add(i, String.join(" ", parts)); + } + + Files.write(atPath, String.join("\n", output).getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE); + } + } + } + + private static String remapDescriptor(String original, UnaryOperator<String> classMappings) { + try { + StringReader reader = new StringReader(original); + StringBuilder result = new StringBuilder(); + boolean insideClassName = false; + StringBuilder className = new StringBuilder(); + + while (true) { + int c = reader.read(); + + if (c == -1) { + break; + } + + if ((char) c == ';') { + insideClassName = false; + result.append(classMappings.apply(className.toString())); + } + + if (insideClassName) { + className.append((char) c); + } else { + result.append((char) c); + } + + if (!insideClassName && (char) c == 'L') { + insideClassName = true; + className.setLength(0); + } + } + + return result.toString(); + } catch (IOException e) { + throw new AssertionError(e); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/util/srg/CoreModClassRemapper.java b/src/main/java/net/fabricmc/loom/util/srg/CoreModClassRemapper.java new file mode 100644 index 00000000..535bcb21 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/srg/CoreModClassRemapper.java @@ -0,0 +1,115 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2020-2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.util.srg; + +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.google.common.collect.ImmutableMap; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import org.gradle.api.logging.Logger; + +import net.fabricmc.loom.util.function.CollectionUtil; +import net.fabricmc.mappingio.tree.MappingTree; + +/** + * Remaps coremod class names from SRG to Yarn. + * + * @author Juuz + */ +public final class CoreModClassRemapper { + private static final Pattern CLASS_NAME_PATTERN = Pattern.compile("^(.*')((?:com\\.mojang\\.|net\\.minecraft\\.)[A-Za-z0-9.-_$]+)('.*)$"); + + public static void remapJar(Path jar, MappingTree mappings, Logger logger) throws IOException { + try (FileSystem fs = FileSystems.newFileSystem(URI.create("jar:" + jar.toUri()), ImmutableMap.of("create", false))) { + Path coremodsJsonPath = fs.getPath("META-INF", "coremods.json"); + + if (Files.notExists(coremodsJsonPath)) { + logger.info(":no coremods in " + jar.getFileName()); + return; + } + + JsonObject coremodsJson; + + try (Reader reader = Files.newBufferedReader(coremodsJsonPath)) { + coremodsJson = new Gson().fromJson(reader, JsonObject.class); + } + + for (Map.Entry<String, JsonElement> nameFileEntry : coremodsJson.entrySet()) { + String file = nameFileEntry.getValue().getAsString(); + Path js = fs.getPath(file); + + if (Files.exists(js)) { + logger.info(":remapping coremod '" + file + "'"); + remap(js, mappings); + } else { + logger.warn("Coremod '" + file + "' listed in coremods.json but not found"); + } + } + } + } + + public static void remap(Path js, MappingTree mappings) throws IOException { + List<String> lines = Files.readAllLines(js); + List<String> output = new ArrayList<>(lines); + + for (int i = 0; i < lines.size(); i++) { + String line = lines.get(i); + Matcher matcher = CLASS_NAME_PATTERN.matcher(line); + + if (matcher.matches()) { + String className = matcher.group(2).replace('.', '/'); + String remapped = CollectionUtil.find(mappings.getClasses(), def -> def.getName("srg").equals(className)) + .map(def -> def.getName("named")) + .orElse(className); + + if (!className.equals(remapped)) { + output.set(i, matcher.group(1) + remapped.replace('/', '.') + matcher.group(3)); + } + } + } + + if (!lines.equals(output)) { + try (Writer writer = Files.newBufferedWriter(js, StandardCharsets.UTF_8, StandardOpenOption.WRITE)) { + writer.write(String.join("\n", output)); + } + } + } +} diff --git a/src/main/java/net/fabricmc/loom/util/srg/InnerClassRemapper.java b/src/main/java/net/fabricmc/loom/util/srg/InnerClassRemapper.java new file mode 100644 index 00000000..e769c2f4 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/srg/InnerClassRemapper.java @@ -0,0 +1,101 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.util.srg; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import dev.architectury.tinyremapper.IMappingProvider; + +import net.fabricmc.loom.util.FileSystemUtil; +import net.fabricmc.mappingio.tree.MappingTree; + +public class InnerClassRemapper { + public static IMappingProvider of(Set<String> fromClassNames, MappingTree mappingsWithSrg, String from, String to) throws IOException { + return sink -> { + remapInnerClass(fromClassNames, mappingsWithSrg, from, to, sink::acceptClass); + }; + } + + public static Set<String> readClassNames(Path jar) { + Set<String> set = new HashSet<>(); + + try (FileSystemUtil.Delegate system = FileSystemUtil.getJarFileSystem(jar, false)) { + Iterator<Path> iterator = Files.walk(system.get().getPath("/")).iterator(); + + while (iterator.hasNext()) { + Path path = iterator.next(); + String name = path.toString(); + if (name.startsWith("/")) name = name.substring(1); + + if (!Files.isDirectory(path) && name.contains("$") && name.endsWith(".class")) { + String className = name.substring(0, name.length() - 6); + set.add(className); + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } + + return set; + } + + private static void remapInnerClass(Set<String> classNames, MappingTree mappingsWithSrg, String from, String to, BiConsumer<String, String> action) { + BiMap<String, String> availableClasses = HashBiMap.create(mappingsWithSrg.getClasses().stream() + .collect(Collectors.groupingBy(classDef -> classDef.getName(from), + Collectors.<MappingTree.ClassMapping, String>reducing( + null, + classDef -> classDef.getName(to), + (first, last) -> last + )) + )); + + for (String className : classNames) { + if (!availableClasses.containsKey(className)) { + String parentName = className.substring(0, className.indexOf('$')); + String childName = className.substring(className.indexOf('$') + 1); + String remappedParentName = availableClasses.getOrDefault(parentName, parentName); + String remappedName = remappedParentName + "$" + childName; + + if (!className.equals(remappedName)) { + if (availableClasses.containsValue(remappedName)) { + // https://github.com/MinecraftForge/MinecraftForge/blob/b027a92dd287d6810a9fdae4d4b1e1432d7dc9cc/patches/minecraft/net/minecraft/Util.java.patch#L8 + action.accept(className, remappedName + "_UNBREAK"); + } else { + action.accept(className, remappedName); + } + } + } + } + } +} diff --git a/src/main/java/net/fabricmc/loom/util/srg/MCPReader.java b/src/main/java/net/fabricmc/loom/util/srg/MCPReader.java new file mode 100644 index 00000000..96382bb5 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/srg/MCPReader.java @@ -0,0 +1,363 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2020-2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.util.srg; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.opencsv.CSVReader; +import com.opencsv.exceptions.CsvValidationException; +import org.apache.commons.io.IOUtils; +import org.cadixdev.lorenz.MappingSet; +import org.cadixdev.lorenz.io.srg.tsrg.TSrgReader; +import org.cadixdev.lorenz.model.ClassMapping; +import org.cadixdev.lorenz.model.FieldMapping; +import org.cadixdev.lorenz.model.InnerClassMapping; +import org.cadixdev.lorenz.model.MethodMapping; +import org.cadixdev.lorenz.model.TopLevelClassMapping; +import org.jetbrains.annotations.Nullable; + +import net.fabricmc.mappingio.MappingReader; +import net.fabricmc.mappingio.tree.MappingTree; +import net.fabricmc.mappingio.tree.MemoryMappingTree; +import net.fabricmc.stitch.commands.tinyv2.TinyClass; +import net.fabricmc.stitch.commands.tinyv2.TinyField; +import net.fabricmc.stitch.commands.tinyv2.TinyFile; +import net.fabricmc.stitch.commands.tinyv2.TinyMethod; +import net.fabricmc.stitch.commands.tinyv2.TinyMethodParameter; +import net.fabricmc.stitch.commands.tinyv2.TinyV2Reader; + +public class MCPReader { + private final Path intermediaryTinyPath; + private final Path srgTsrgPath; + + public MCPReader(Path intermediaryTinyPath, Path srgTsrgPath) { + this.intermediaryTinyPath = intermediaryTinyPath; + this.srgTsrgPath = srgTsrgPath; + } + + public TinyFile read(Path mcpJar) throws IOException { + Map<MemberToken, String> srgTokens = readSrg(); + TinyFile intermediaryTiny = TinyV2Reader.read(intermediaryTinyPath); + Map<String, String> intermediaryToMCPMap = createIntermediaryToMCPMap(intermediaryTiny, srgTokens); + Map<String, String[]> intermediaryToDocsMap = new HashMap<>(); + Map<String, Map<Integer, String>> intermediaryToParamsMap = new HashMap<>(); + + try { + injectMcp(mcpJar, intermediaryToMCPMap, intermediaryToDocsMap, intermediaryToParamsMap); + } catch (CsvValidationException e) { + throw new RuntimeException(e); + } + + mergeTokensIntoIntermediary(intermediaryTiny, intermediaryToMCPMap, intermediaryToDocsMap, intermediaryToParamsMap); + return intermediaryTiny; + } + + private Map<String, String> createIntermediaryToMCPMap(TinyFile tiny, Map<MemberToken, String> officialToMCP) { + Map<String, String> map = new HashMap<>(); + + for (TinyClass tinyClass : tiny.getClassEntries()) { + String classObf = tinyClass.getMapping().get(0); + String classIntermediary = tinyClass.getMapping().get(1); + MemberToken classTokenObf = MemberToken.ofClass(classObf); + + if (officialToMCP.containsKey(classTokenObf)) { + map.put(classIntermediary, officialToMCP.get(classTokenObf)); + } + + for (TinyField tinyField : tinyClass.getFields()) { + String fieldObf = tinyField.getMapping().get(0); + String fieldIntermediary = tinyField.getMapping().get(1); + MemberToken fieldTokenObf = MemberToken.ofField(classTokenObf, fieldObf); + + if (officialToMCP.containsKey(fieldTokenObf)) { + map.put(fieldIntermediary, officialToMCP.get(fieldTokenObf)); + } + } + + for (TinyMethod tinyMethod : tinyClass.getMethods()) { + String methodObf = tinyMethod.getMapping().get(0); + String methodIntermediary = tinyMethod.getMapping().get(1); + MemberToken methodTokenObf = MemberToken.ofMethod(classTokenObf, methodObf, tinyMethod.getMethodDescriptorInFirstNamespace()); + + if (officialToMCP.containsKey(methodTokenObf)) { + map.put(methodIntermediary, officialToMCP.get(methodTokenObf)); + } + } + } + + return map; + } + + private void mergeTokensIntoIntermediary(TinyFile tiny, Map<String, String> intermediaryToMCPMap, Map<String, String[]> intermediaryToDocsMap, Map<String, Map<Integer, String>> intermediaryToParamsMap) { + stripTinyWithParametersAndLocal(tiny); + + // We will be adding the "named" namespace with MCP + tiny.getHeader().getNamespaces().add("named"); + + for (TinyClass tinyClass : tiny.getClassEntries()) { + String classIntermediary = tinyClass.getMapping().get(1); + tinyClass.getMapping().add(intermediaryToMCPMap.getOrDefault(classIntermediary, classIntermediary)); + + for (TinyField tinyField : tinyClass.getFields()) { + String fieldIntermediary = tinyField.getMapping().get(1); + String[] docs = intermediaryToDocsMap.get(fieldIntermediary); + tinyField.getMapping().add(intermediaryToMCPMap.getOrDefault(fieldIntermediary, fieldIntermediary)); + + if (docs != null) { + tinyField.getComments().clear(); + tinyField.getComments().addAll(Arrays.asList(docs)); + } + } + + for (TinyMethod tinyMethod : tinyClass.getMethods()) { + String methodIntermediary = tinyMethod.getMapping().get(1); + String[] docs = intermediaryToDocsMap.get(methodIntermediary); + tinyMethod.getMapping().add(intermediaryToMCPMap.getOrDefault(methodIntermediary, methodIntermediary)); + + if (docs != null) { + tinyMethod.getComments().clear(); + tinyMethod.getComments().addAll(Arrays.asList(docs)); + } + + Map<Integer, String> params = intermediaryToParamsMap.get(methodIntermediary); + + if (params != null) { + for (Map.Entry<Integer, String> entry : params.entrySet()) { + int lvIndex = entry.getKey(); + String paramName = entry.getValue(); + + ArrayList<String> mappings = new ArrayList<>(); + mappings.add(""); + mappings.add(""); + mappings.add(paramName); + tinyMethod.getParameters().add(new TinyMethodParameter(lvIndex, mappings, new ArrayList<>())); + } + } + } + } + } + + private void stripTinyWithParametersAndLocal(TinyFile tiny) { + for (TinyClass tinyClass : tiny.getClassEntries()) { + for (TinyMethod tinyMethod : tinyClass.getMethods()) { + tinyMethod.getParameters().clear(); + tinyMethod.getLocalVariables().clear(); + } + } + } + + private Map<MemberToken, String> readSrg() throws IOException { + Map<MemberToken, String> tokens = new HashMap<>(); + + try (BufferedReader reader = Files.newBufferedReader(srgTsrgPath, StandardCharsets.UTF_8)) { + String content = IOUtils.toString(reader); + + if (content.startsWith("tsrg2")) { + readTsrg2(tokens, content); + } else { + MappingSet mappingSet = new TSrgReader(new StringReader(content)).read(); + + for (TopLevelClassMapping classMapping : mappingSet.getTopLevelClassMappings()) { + appendClass(tokens, classMapping); + } + } + } + + return tokens; + } + + private void readTsrg2(Map<MemberToken, String> tokens, String content) throws IOException { + MemoryMappingTree tree = new MemoryMappingTree(); + MappingReader.read(new StringReader(content), tree); + int obfIndex = tree.getNamespaceId("obf"); + int srgIndex = tree.getNamespaceId("srg"); + + for (MappingTree.ClassMapping classDef : tree.getClasses()) { + MemberToken ofClass = MemberToken.ofClass(classDef.getName(obfIndex)); + tokens.put(ofClass, classDef.getName(srgIndex)); + + for (MappingTree.FieldMapping fieldDef : classDef.getFields()) { + tokens.put(MemberToken.ofField(ofClass, fieldDef.getName(obfIndex)), fieldDef.getName(srgIndex)); + } + + for (MappingTree.MethodMapping methodDef : classDef.getMethods()) { + tokens.put(MemberToken.ofMethod(ofClass, methodDef.getName(obfIndex), methodDef.getDesc(obfIndex)), + methodDef.getName(srgIndex)); + } + } + } + + private void injectMcp(Path mcpJar, Map<String, String> intermediaryToSrgMap, Map<String, String[]> intermediaryToDocsMap, Map<String, Map<Integer, String>> intermediaryToParamsMap) + throws IOException, CsvValidationException { + Map<String, List<String>> srgToIntermediary = inverseMap(intermediaryToSrgMap); + Map<String, List<String>> simpleSrgToIntermediary = new HashMap<>(); + Pattern methodPattern = Pattern.compile("(func_\\d*)_.*"); + + for (Map.Entry<String, List<String>> entry : srgToIntermediary.entrySet()) { + Matcher matcher = methodPattern.matcher(entry.getKey()); + + if (matcher.matches()) { + simpleSrgToIntermediary.put(matcher.group(1), entry.getValue()); + } + } + + try (FileSystem fs = FileSystems.newFileSystem(mcpJar, (ClassLoader) null)) { + Path fields = fs.getPath("fields.csv"); + Path methods = fs.getPath("methods.csv"); + Path params = fs.getPath("params.csv"); + Pattern paramsPattern = Pattern.compile("p_[^\\d]*(\\d+)_(\\d)+_?"); + + try (CSVReader reader = new CSVReader(Files.newBufferedReader(fields, StandardCharsets.UTF_8))) { + reader.readNext(); + String[] line; + + while ((line = reader.readNext()) != null) { + List<String> intermediaryField = srgToIntermediary.get(line[0]); + String[] docs = line[3].split("\n"); + + if (intermediaryField != null) { + for (String s : intermediaryField) { + intermediaryToSrgMap.put(s, line[1]); + + if (!line[3].trim().isEmpty() && docs.length > 0) { + intermediaryToDocsMap.put(s, docs); + } + } + } + } + } + + try (CSVReader reader = new CSVReader(Files.newBufferedReader(methods, StandardCharsets.UTF_8))) { + reader.readNext(); + String[] line; + + while ((line = reader.readNext()) != null) { + List<String> intermediaryMethod = srgToIntermediary.get(line[0]); + String[] docs = line[3].split("\n"); + + if (intermediaryMethod != null) { + for (String s : intermediaryMethod) { + intermediaryToSrgMap.put(s, line[1]); + + if (!line[3].trim().isEmpty() && docs.length > 0) { + intermediaryToDocsMap.put(s, docs); + } + } + } + } + } + + if (Files.exists(params)) { + try (CSVReader reader = new CSVReader(Files.newBufferedReader(params, StandardCharsets.UTF_8))) { + reader.readNext(); + String[] line; + + while ((line = reader.readNext()) != null) { + Matcher param = paramsPattern.matcher(line[0]); + + if (param.matches()) { + String named = line[1]; + String srgMethodStartWith = "func_" + param.group(1); + int lvIndex = Integer.parseInt(param.group(2)); + List<String> intermediaryMethod = simpleSrgToIntermediary.get(srgMethodStartWith); + + if (intermediaryMethod != null) { + for (String s : intermediaryMethod) { + intermediaryToParamsMap.computeIfAbsent(s, s1 -> new HashMap<>()).put(lvIndex, named); + } + } + } + } + } + } + } + } + + private Map<String, List<String>> inverseMap(Map<String, String> intermediaryToMCPMap) { + Map<String, List<String>> map = new HashMap<>(); + + for (Map.Entry<String, String> token : intermediaryToMCPMap.entrySet()) { + map.computeIfAbsent(token.getValue(), s -> new ArrayList<>()).add(token.getKey()); + } + + return map; + } + + private void appendClass(Map<MemberToken, String> tokens, ClassMapping<?, ?> classMapping) { + MemberToken ofClass = MemberToken.ofClass(classMapping.getFullObfuscatedName()); + tokens.put(ofClass, classMapping.getFullDeobfuscatedName()); + + for (FieldMapping fieldMapping : classMapping.getFieldMappings()) { + tokens.put(MemberToken.ofField(ofClass, fieldMapping.getObfuscatedName()), fieldMapping.getDeobfuscatedName()); + } + + for (MethodMapping methodMapping : classMapping.getMethodMappings()) { + tokens.put(MemberToken.ofMethod(ofClass, methodMapping.getObfuscatedName(), methodMapping.getObfuscatedDescriptor()), methodMapping.getDeobfuscatedName()); + } + + for (InnerClassMapping mapping : classMapping.getInnerClassMappings()) { + appendClass(tokens, mapping); + } + } + + private record MemberToken( + TokenType type, + @Nullable MCPReader.MemberToken owner, + String name, + @Nullable String descriptor + ) { + static MemberToken ofClass(String name) { + return new MemberToken(TokenType.CLASS, null, name, null); + } + + static MemberToken ofField(MemberToken owner, String name) { + return new MemberToken(TokenType.FIELD, owner, name, null); + } + + static MemberToken ofMethod(MemberToken owner, String name, String descriptor) { + return new MemberToken(TokenType.METHOD, owner, name, descriptor); + } + } + + private enum TokenType { + CLASS, + METHOD, + FIELD + } +} diff --git a/src/main/java/net/fabricmc/loom/util/srg/MappingException.java b/src/main/java/net/fabricmc/loom/util/srg/MappingException.java new file mode 100644 index 00000000..5bf2a63b --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/srg/MappingException.java @@ -0,0 +1,36 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2020-2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.util.srg; + +/** + * An exception that occurs when processing obfuscation mappings. + * + * @author Juuz + */ +public class MappingException extends RuntimeException { + public MappingException(String message) { + super(message); + } +} diff --git a/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java b/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java new file mode 100644 index 00000000..59392e94 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java @@ -0,0 +1,148 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.util.srg; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import com.google.common.base.Stopwatch; +import org.apache.commons.io.output.NullOutputStream; +import org.gradle.api.Project; +import org.gradle.api.logging.LogLevel; +import org.gradle.api.logging.configuration.ShowStacktrace; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.configuration.providers.forge.McpConfigProvider.RemapAction; +import net.fabricmc.loom.util.FileSystemUtil; +import net.fabricmc.loom.util.ThreadingUtils; + +public class SpecialSourceExecutor { + private static String trimLeadingSlash(String string) { + if (string.startsWith(File.separator)) { + return string.substring(File.separator.length()); + } else if (string.startsWith("/")) { + return string.substring(1); + } + + return string; + } + + public static Path produceSrgJar(RemapAction remapAction, Project project, String side, Set<File> mcLibs, Path officialJar, Path mappings) + throws Exception { + Set<String> filter = Files.readAllLines(mappings, StandardCharsets.UTF_8).stream() + .filter(s -> !s.startsWith("\t")) + .map(s -> s.split(" ")[0] + ".class") + .collect(Collectors.toSet()); + LoomGradleExtension extension = LoomGradleExtension.get(project.getProject()); + Path stripped = extension.getFiles().getProjectBuildCache().toPath().resolve(officialJar.getFileName().toString().substring(0, officialJar.getFileName().toString().length() - 4) + "-filtered.jar"); + Files.deleteIfExists(stripped); + + Stopwatch stopwatch = Stopwatch.createStarted(); + + try (FileSystemUtil.Delegate output = FileSystemUtil.getJarFileSystem(stripped, true)) { + try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(officialJar, false)) { + ThreadingUtils.TaskCompleter completer = ThreadingUtils.taskCompleter(); + + for (Path path : (Iterable<? extends Path>) Files.walk(fs.get().getPath("/"))::iterator) { + String trimLeadingSlash = trimLeadingSlash(path.toString()); + if (!trimLeadingSlash.endsWith(".class")) continue; + boolean has = filter.contains(trimLeadingSlash); + String s = trimLeadingSlash; + + while (s.contains("$") && !has) { + s = s.substring(0, s.lastIndexOf("$")) + ".class"; + has = filter.contains(s); + } + + if (!has) continue; + Path to = output.get().getPath(trimLeadingSlash); + Path parent = to.getParent(); + if (parent != null) Files.createDirectories(parent); + + completer.add(() -> { + Files.copy(path, to, StandardCopyOption.COPY_ATTRIBUTES); + }); + } + + completer.complete(); + } + } finally { + project.getLogger().info("Copied class files in " + stopwatch.stop()); + } + + Path output = extension.getFiles().getProjectBuildCache().toPath().resolve(officialJar.getFileName().toString().substring(0, officialJar.getFileName().toString().length() - 4) + "-srg-output.jar"); + Files.deleteIfExists(output); + stopwatch = Stopwatch.createStarted(); + + List<String> args = remapAction.getArgs(stripped, output, mappings, project.files(mcLibs)); + + project.getLogger().lifecycle(":remapping minecraft (" + remapAction + ", " + side + ", official -> mojang)"); + + Path workingDir = tmpDir(); + + project.javaexec(spec -> { + spec.setArgs(args); + spec.setClasspath(remapAction.getClasspath()); + spec.workingDir(workingDir.toFile()); + spec.getMainClass().set(remapAction.getMainClass()); + + // if running with INFO or DEBUG logging + if (project.getGradle().getStartParameter().getShowStacktrace() != ShowStacktrace.INTERNAL_EXCEPTIONS + || project.getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0) { + spec.setStandardOutput(System.out); + spec.setErrorOutput(System.err); + } else { + spec.setStandardOutput(NullOutputStream.NULL_OUTPUT_STREAM); + spec.setErrorOutput(NullOutputStream.NULL_OUTPUT_STREAM); + } + }).rethrowFailure().assertNormalExitValue(); + + project.getLogger().lifecycle(":remapped minecraft (" + remapAction + ", " + side + ", official -> mojang) in " + stopwatch.stop()); + + Files.deleteIfExists(stripped); + + Path tmp = tmpFile(); + Files.deleteIfExists(tmp); + Files.copy(output, tmp); + + Files.deleteIfExists(output); + return tmp; + } + + private static Path tmpFile() throws IOException { + return Files.createTempFile(null, null); + } + + private static Path tmpDir() throws IOException { + return Files.createTempDirectory(null); + } +} diff --git a/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java b/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java new file mode 100644 index 00000000..ede2dbad --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java @@ -0,0 +1,406 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2020-2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.util.srg; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.google.common.base.MoreObjects; +import com.google.common.base.Stopwatch; +import org.apache.commons.io.IOUtils; +import org.gradle.api.logging.Logger; +import org.jetbrains.annotations.Nullable; + +import net.fabricmc.loom.util.function.CollectionUtil; +import net.fabricmc.mappingio.FlatMappingVisitor; +import net.fabricmc.mappingio.MappingReader; +import net.fabricmc.mappingio.adapter.RegularAsFlatMappingVisitor; +import net.fabricmc.mappingio.format.Tiny2Writer; +import net.fabricmc.mappingio.format.TsrgReader; +import net.fabricmc.mappingio.tree.MappingTree; +import net.fabricmc.mappingio.tree.MappingTreeView; +import net.fabricmc.mappingio.tree.MemoryMappingTree; + +/** + * Utilities for merging SRG mappings. + * + * @author Juuz + */ +public final class SrgMerger { + private final Logger logger; + private final Map<String, List<MappingTreeView.MemberMappingView>> addRegardlessSrg = new HashMap<>(); + private final MemoryMappingTree srg; + private final MemoryMappingTree src; + private final MemoryMappingTree output; + private final FlatMappingVisitor flatOutput; + private final List<Runnable> postProcesses = new ArrayList<>(); + private final boolean lenient; + private final Set<String> methodSrgNames = new HashSet<>(); + + public SrgMerger(Logger logger, Path srg, @Nullable Supplier<Path> mojmap, Path tiny, boolean lenient) throws IOException { + this.logger = logger; + this.srg = readSrg(srg, mojmap); + this.src = new MemoryMappingTree(); + this.output = new MemoryMappingTree(); + this.flatOutput = new RegularAsFlatMappingVisitor(output); + this.lenient = lenient; + + MappingReader.read(tiny, this.src); + + if (!"official".equals(this.src.getSrcNamespace())) { + throw new MappingException("Mapping file " + tiny + " does not have the 'official' namespace as the default!"); + } + + this.output.visitNamespaces(this.src.getSrcNamespace(), Stream.concat(Stream.of("srg"), this.src.getDstNamespaces().stream()).collect(Collectors.toList())); + } + + public MemoryMappingTree merge() throws IOException { + for (MappingTree.ClassMapping klass : this.srg.getClasses()) { + classToTiny(klass); + } + + try { + for (Runnable process : postProcesses) { + process.run(); + } + } catch (UncheckedIOException e) { + throw e.getCause(); + } + + return output; + } + + /** + * Merges SRG mappings with a tiny mappings tree through the obf names. + * + * @param srg the SRG file in .tsrg format + * @param mojmap the path to the mojmap file used for generating mojmap+srg names, may be null + * @param tiny the tiny file + * @param out the output file, will be in tiny v2 + * @param lenient whether to ignore missing tiny mapping + * @throws IOException if an IO error occurs while reading or writing the mappings + * @throws MappingException if the input tiny tree's default namespace is not 'official' + * or if an element mentioned in the SRG file does not have tiny mappings + */ + public static void mergeSrg(Logger logger, @Nullable Supplier<Path> mojmap, Path srg, Path tiny, Path out, boolean lenient) + throws IOException, MappingException { + Stopwatch stopwatch = Stopwatch.createStarted(); + MemoryMappingTree tree = mergeSrg(logger, mojmap, srg, tiny, lenient); + + try (Tiny2Writer writer = new Tiny2Writer(Files.newBufferedWriter(out), false)) { + tree.accept(writer); + } catch (IOException e) { + e.printStackTrace(); + } + + logger.info(":merged srg mappings in " + stopwatch.stop()); + } + + /** + * Merges SRG mappings with a tiny mappings tree through the obf names. + * + * @param srg the SRG file in .tsrg format + * @param mojmap the path to the mojmap file used for generating mojmap+srg names, may be null + * @param tiny the tiny file + * @param lenient whether to ignore missing tiny mapping + * @return the created mapping tree + * @throws IOException if an IO error occurs while reading or writing the mappings + * @throws MappingException if the input tiny tree's default namespace is not 'official' + * or if an element mentioned in the SRG file does not have tiny mappings + */ + public static MemoryMappingTree mergeSrg(Logger logger, @Nullable Supplier<Path> mojmap, Path srg, Path tiny, boolean lenient) + throws IOException, MappingException { + return new SrgMerger(logger, srg, mojmap, tiny, lenient).merge(); + } + + private MemoryMappingTree readSrg(Path srg, @Nullable Supplier<Path> mojmap) throws IOException { + try (BufferedReader reader = Files.newBufferedReader(srg)) { + String content = IOUtils.toString(reader); + + if (content.startsWith("tsrg2") && mojmap != null) { + addRegardlessSrgs(mojmap); + } + + MemoryMappingTree tsrg = new MemoryMappingTree(); + TsrgReader.read(new StringReader(content), tsrg); + return tsrg; + } + } + + private void addRegardlessSrgs(Supplier<Path> mojmap) throws IOException { + MemoryMappingTree mojmapTree = readTsrg2ToTinyTree(mojmap.get()); + + for (MappingTree.ClassMapping classDef : mojmapTree.getClasses()) { + for (MappingTree.MethodMapping methodDef : classDef.getMethods()) { + String name = methodDef.getSrcName(); + + if (name.indexOf('<') != 0 && name.equals(methodDef.getDstName(0))) { + addRegardlessSrg.computeIfAbsent(classDef.getSrcName(), $ -> new ArrayList<>()).add(methodDef); + } + } + + for (MappingTree.FieldMapping fieldDef : classDef.getFields()) { + if (fieldDef.getSrcName().equals(fieldDef.getDstName(0))) { + addRegardlessSrg.computeIfAbsent(classDef.getSrcName(), $ -> new ArrayList<>()).add(fieldDef); + } + } + } + } + + private static MemoryMappingTree readTsrg2ToTinyTree(Path path) throws IOException { + MemoryMappingTree tree = new MemoryMappingTree(); + + try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) { + MappingReader.read(reader, tree); + } + + return tree; + } + + private void classToTiny(MappingTree.ClassMapping klass) throws IOException { + String obf = klass.getSrcName(); + String srg = klass.getDstName(0); + MappingTree.ClassMapping classDef = this.src.getClass(obf); + + if (classDef == null) { + if (lenient) { + return; + } else { + throw new MappingException("Missing class: " + obf + " (srg: " + srg + ")"); + } + } + + List<String> classNames = CollectionUtil.map( + output.getDstNamespaces(), + namespace -> "srg".equals(namespace) ? srg : classDef.getName(namespace) + ); + + flatOutput.visitClass(obf, classNames.toArray(new String[0])); + + if (classDef.getComment() != null) { + flatOutput.visitClassComment(obf, classDef.getComment()); + } + + for (MappingTree.MethodMapping method : klass.getMethods()) { + MappingTree.MethodMapping def = CollectionUtil.find( + classDef.getMethods(), + m -> m.getName("official").equals(method.getSrcName()) && m.getDesc("official").equals(method.getSrcDesc()) + ).orElse(null); + + String methodSrgName = method.getDstName(0); + + if (def == null) { + if (lenient) { + // TODO Big Hack! + // We are checking if there are methods with the same srg name that are already added, if it is, then we would not fill in these names + // This is especially troublesome with methods annotated with @DontObfuscate (e.g. m_129629_) + // with environments like yarn where methods with the same srg name may not inherit the same names due to parameter mappings and inheritance + // This requires further testing! + postProcesses.add(() -> { + if (!methodSrgNames.contains(methodSrgName)) { + List<String> methodNames = CollectionUtil.map( + output.getDstNamespaces(), + namespace -> "srg".equals(namespace) ? methodSrgName : method.getSrcName() + ); + + try { + flatOutput.visitMethod(obf, method.getSrcName(), method.getSrcDesc(), methodNames.toArray(new String[0])); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + }); + } else { + throw new MappingException("Missing method: " + method.getSrcName() + " (srg: " + methodSrgName + ")"); + } + + continue; + } + + methodToTiny(obf, method, methodSrgName, def); + + if (methodSrgName.startsWith("func_") || methodSrgName.startsWith("m_")) { + methodSrgNames.add(methodSrgName); + } + } + + postProcesses.add(() -> { + // TODO: This second iteration seems a bit wasteful. + // Is it possible to just iterate this and leave SRG out? + for (MappingTree.MethodMapping def : classDef.getMethods()) { + // If obf = some other name: some special name that srg might not contain. + // This includes constructors and overridden JDK methods. + if (!def.getSrcName().equals(def.getDstName(0))) { + continue; + } + + MappingTree.MethodMapping method = CollectionUtil.find( + klass.getMethods(), + m -> m.getSrcName().equals(def.getName("official")) && m.getSrcDesc().equals(def.getDesc("official")) + ).orElse(null); + + if (method == null) { + try { + methodToTiny(obf, null, def.getSrcName(), def); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + } + }); + + for (MappingTree.FieldMapping field : klass.getFields()) { + MappingTree.FieldMapping def = CollectionUtil.find( + classDef.getFields(), + f -> f.getName("official").equals(field.getSrcName()) + ).orElse(nullOrThrow(() -> new MappingException("Missing field: " + field.getSrcName() + " (srg: " + field.getDstName(0) + ")"))); + + if (def == null) { + if (tryMatchRegardlessSrgsField(obf, field)) { + List<String> fieldNames = CollectionUtil.map( + output.getDstNamespaces(), + namespace -> "srg".equals(namespace) ? field.getDstName(0) : field.getSrcName() + ); + + flatOutput.visitField(obf, field.getSrcName(), field.getSrcDesc(), fieldNames.toArray(new String[0])); + } + + continue; + } + + List<String> fieldNames = CollectionUtil.map( + output.getDstNamespaces(), + namespace -> "srg".equals(namespace) ? field.getDstName(0) : def.getName(namespace) + ); + + flatOutput.visitField(obf, def.getName("official"), def.getDesc("official"), fieldNames.toArray(new String[0])); + + if (def.getComment() != null) { + flatOutput.visitFieldComment(obf, def.getName("official"), def.getDesc("official"), def.getComment()); + } + } + } + + private void methodToTiny(String obfClassName, @Nullable MappingTree.MethodMapping srgMethod, @Nullable String srgMethodName, MappingTree.MethodMapping actualMethod) + throws IOException { + if (srgMethod != null && srgMethodName != null) { + srgMethodName = srgMethod.getDstName(0); + } + + String finalSrgMethodName = srgMethodName; + List<String> methodNames = CollectionUtil.map( + output.getDstNamespaces(), + namespace -> "srg".equals(namespace) ? finalSrgMethodName : actualMethod.getName(namespace) + ); + + flatOutput.visitMethod(obfClassName, actualMethod.getName("official"), actualMethod.getDesc("official"), methodNames.toArray(new String[0])); + + if (actualMethod.getComment() != null) { + flatOutput.visitMethodComment(obfClassName, actualMethod.getName("official"), actualMethod.getDesc("official"), actualMethod.getComment()); + } + + for (MappingTree.MethodArgMapping arg : actualMethod.getArgs()) { + MappingTree.MethodArgMapping srgArg = srgMethod != null ? srgMethod.getArg(arg.getArgPosition(), arg.getLvIndex(), arg.getName("official")) : null; + String srgName = srgArg != null ? srgArg.getDstName(0) : null; + List<String> argNames = CollectionUtil.map( + output.getDstNamespaces(), + namespace -> "srg".equals(namespace) ? srgName : arg.getName(namespace) + ); + + flatOutput.visitMethodArg(obfClassName, actualMethod.getName("official"), actualMethod.getDesc("official"), arg.getArgPosition(), arg.getLvIndex(), arg.getName("official"), argNames.toArray(new String[0])); + + if (arg.getComment() != null) { + flatOutput.visitMethodArgComment(obfClassName, actualMethod.getName("official"), actualMethod.getDesc("official"), arg.getArgPosition(), arg.getLvIndex(), arg.getName("official"), arg.getComment()); + } + } + + for (MappingTree.MethodVarMapping var : actualMethod.getVars()) { + MappingTree.MethodVarMapping srgVar = srgMethod != null ? srgMethod.getVar(var.getLvtRowIndex(), var.getLvIndex(), var.getStartOpIdx(), var.getName("official")) : null; + String srgName = srgVar != null ? srgVar.getDstName(0) : null; + List<String> varNames = CollectionUtil.map( + output.getDstNamespaces(), + namespace -> "srg".equals(namespace) ? srgName : var.getName(namespace) + ); + + flatOutput.visitMethodVar(obfClassName, actualMethod.getName("official"), actualMethod.getDesc("official"), var.getLvtRowIndex(), var.getLvIndex(), var.getStartOpIdx(), var.getName("official"), varNames.toArray(new String[0])); + + if (var.getComment() != null) { + flatOutput.visitMethodVarComment(obfClassName, actualMethod.getName("official"), actualMethod.getDesc("official"), var.getLvtRowIndex(), var.getLvIndex(), var.getStartOpIdx(), var.getName("official"), var.getComment()); + } + } + } + + private boolean tryMatchRegardlessSrgsMethod(String obf, MappingTree.MethodMapping method) { + List<MappingTreeView.MemberMappingView> mutableDescriptoredList = addRegardlessSrg.get(obf); + + if (!method.getDstName(0).equals(method.getSrcName())) { + for (MappingTreeView.MemberMappingView descriptored : MoreObjects.firstNonNull(mutableDescriptoredList, Collections.<MappingTreeView.MemberMappingView>emptyList())) { + if (descriptored instanceof MappingTree.MethodMapping && descriptored.getSrcName().equals(method.getSrcName()) && descriptored.getSrcDesc().equals(method.getSrcDesc())) { + return true; + } + } + } + + return false; + } + + private boolean tryMatchRegardlessSrgsField(String obf, MappingTree.FieldMapping field) { + List<MappingTreeView.MemberMappingView> mutableDescriptoredList = addRegardlessSrg.get(obf); + + if (!field.getDstName(0).equals(field.getSrcName())) { + for (MappingTreeView.MemberMappingView descriptored : MoreObjects.firstNonNull(mutableDescriptoredList, Collections.<MappingTreeView.MemberMappingView>emptyList())) { + if (descriptored instanceof MappingTree.FieldMapping && descriptored.getSrcName().equals(field.getSrcName())) { + return true; + } + } + } + + return false; + } + + @Nullable + private <T, X extends Exception> T nullOrThrow(Supplier<X> exception) throws X { + if (lenient) { + return null; + } else { + throw exception.get(); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/util/srg/SrgNamedWriter.java b/src/main/java/net/fabricmc/loom/util/srg/SrgNamedWriter.java new file mode 100644 index 00000000..456b8b1b --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/srg/SrgNamedWriter.java @@ -0,0 +1,47 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.util.srg; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.cadixdev.lorenz.io.srg.SrgWriter; +import org.gradle.api.logging.Logger; + +import net.fabricmc.lorenztiny.TinyMappingsReader; +import net.fabricmc.mappingio.tree.MappingTree; + +public class SrgNamedWriter { + public static void writeTo(Logger logger, Path srgFile, MappingTree mappings, String from, String to) throws IOException { + Files.deleteIfExists(srgFile); + + try (SrgWriter writer = new SrgWriter(Files.newBufferedWriter(srgFile))) { + try (TinyMappingsReader reader = new TinyMappingsReader(mappings, from, to)) { + writer.write(reader.read()); + } + } + } +} diff --git a/src/main/java/net/fabricmc/loom/util/srg/Tsrg2Utils.java b/src/main/java/net/fabricmc/loom/util/srg/Tsrg2Utils.java new file mode 100644 index 00000000..4ed3a884 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/srg/Tsrg2Utils.java @@ -0,0 +1,144 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.util.srg; + +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import org.cadixdev.lorenz.MappingSet; +import org.cadixdev.lorenz.io.srg.tsrg.TSrgWriter; +import org.cadixdev.lorenz.model.ClassMapping; +import org.cadixdev.lorenz.model.MethodMapping; + +import net.fabricmc.mappingio.MappingVisitor; +import net.fabricmc.mappingio.MappingWriter; +import net.fabricmc.mappingio.adapter.ForwardingMappingVisitor; +import net.fabricmc.mappingio.tree.MappingTree; +import net.fabricmc.mappingio.tree.MemoryMappingTree; + +public class Tsrg2Utils { + public static void writeTsrg(Consumer<MappingVisitor> visitorConsumer, String dstNamespace, boolean applyParameterMappings, Writer writer) + throws IOException { + MappingSet set; + + try (MappingsIO2LorenzWriter lorenzWriter = new MappingsIO2LorenzWriter(dstNamespace, applyParameterMappings)) { + visitorConsumer.accept(lorenzWriter); + set = lorenzWriter.read(); + } + + try (TSrgWriter w = new TSrgWriter(writer)) { + w.write(set); + } + } + + // TODO Move this elsewhere + public abstract static class MappingsIO2Others extends ForwardingMappingVisitor implements MappingWriter { + public MappingsIO2Others() { + super(new MemoryMappingTree()); + } + + public MappingTree tree() { + return (MappingTree) next; + } + + @Override + public void close() throws IOException { + MappingTree tree = tree(); + List<String> names = new ArrayList<>(); + + for (MappingTree.ClassMapping aClass : tree.getClasses()) { + names.add(aClass.getSrcName()); + } + + for (String name : names) { + tree.removeClass(name); + } + } + } + + public static class MappingsIO2LorenzWriter extends MappingsIO2Others { + private final Object dstNamespaceUnresolved; + private int dstNamespace; + private boolean applyParameterMappings; + + public MappingsIO2LorenzWriter(int dstNamespace, boolean applyParameterMappings) { + this.dstNamespaceUnresolved = dstNamespace; + this.applyParameterMappings = applyParameterMappings; + } + + public MappingsIO2LorenzWriter(String dstNamespace, boolean applyParameterMappings) { + this.dstNamespaceUnresolved = dstNamespace; + this.applyParameterMappings = applyParameterMappings; + } + + @Override + public void visitNamespaces(String srcNamespace, List<String> dstNamespaces) throws IOException { + super.visitNamespaces(srcNamespace, dstNamespaces); + this.dstNamespace = dstNamespaceUnresolved instanceof Integer ? (Integer) dstNamespaceUnresolved : dstNamespaces.indexOf((String) dstNamespaceUnresolved); + } + + public MappingSet read() throws IOException { + return this.read(MappingSet.create()); + } + + public MappingSet read(final MappingSet mappings) throws IOException { + MappingTree tree = tree(); + + for (MappingTree.ClassMapping aClass : tree.getClasses()) { + ClassMapping<?, ?> lClass = mappings.getOrCreateClassMapping(aClass.getSrcName()) + .setDeobfuscatedName(aClass.getDstName(dstNamespace)); + + for (MappingTree.FieldMapping aField : aClass.getFields()) { + String srcDesc = aField.getSrcDesc(); + + if (srcDesc == null || srcDesc.isEmpty()) { + lClass.getOrCreateFieldMapping(aField.getSrcName()) + .setDeobfuscatedName(aField.getDstName(dstNamespace)); + } else { + lClass.getOrCreateFieldMapping(aField.getSrcName(), srcDesc) + .setDeobfuscatedName(aField.getDstName(dstNamespace)); + } + } + + for (MappingTree.MethodMapping aMethod : aClass.getMethods()) { + MethodMapping lMethod = lClass.getOrCreateMethodMapping(aMethod.getSrcName(), aMethod.getSrcDesc()) + .setDeobfuscatedName(aMethod.getDstName(dstNamespace)); + + if (applyParameterMappings) { + for (MappingTree.MethodArgMapping aArg : aMethod.getArgs()) { + lMethod.getOrCreateParameterMapping(aArg.getLvIndex()) + .setDeobfuscatedName(aArg.getDstName(dstNamespace)); + } + } + } + } + + return mappings; + } + } +} diff --git a/src/main/java/net/fabricmc/loom/util/srg/Tsrg2Writer.java b/src/main/java/net/fabricmc/loom/util/srg/Tsrg2Writer.java new file mode 100644 index 00000000..7463e447 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/srg/Tsrg2Writer.java @@ -0,0 +1,114 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.util.srg; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import net.fabricmc.mappingio.tree.MappingTree; +import net.fabricmc.mappingio.tree.MappingTreeView; + +public class Tsrg2Writer { + public Tsrg2Writer() { + } + + public static String serialize(MappingTree tree) { + List<String> namespaces = Stream.concat(Stream.of(tree.getSrcNamespace()), tree.getDstNamespaces().stream()).collect(Collectors.toList()); + StringBuilder builder = new StringBuilder(); + writeHeader(namespaces, builder); + + for (MappingTree.ClassMapping classMapping : tree.getClasses()) { + writeClass(namespaces, classMapping, builder); + } + + return builder.toString(); + } + + private static void writeClass(List<String> namespaces, MappingTree.ClassMapping def, StringBuilder builder) { + writeMapped(null, namespaces, def, builder); + + for (MappingTree.MethodMapping method : def.getMethods()) { + writeMethod(namespaces, method, builder); + } + + for (MappingTree.FieldMapping field : def.getFields()) { + writeMapped('\t', namespaces, field, builder); + } + } + + private static void writeMethod(List<String> namespaces, MappingTree.MethodMapping def, StringBuilder builder) { + writeMapped('\t', namespaces, def, builder); + + for (MappingTree.MethodArgMapping arg : def.getArgs()) { + builder.append("\t\t").append(arg.getLvIndex()); + writeMapped(' ', namespaces, arg, builder); + } + } + + private static void writeField(List<String> namespaces, MappingTree.FieldMapping def, StringBuilder builder) { + writeMapped('\t', namespaces, def, builder); + } + + private static void writeMapped(Character first, List<String> namespaces, MappingTreeView.ElementMappingView mapped, StringBuilder builder) { + String[] names = namespaces.stream().map(mapped::getName).toArray(String[]::new); + + for (int i = 0; i < names.length; ++i) { + String name = names[i]; + + if (i == 0) { + if (first != null) { + builder.append(first); + } + } else { + builder.append(' '); + } + + builder.append(name); + + if (i == 0 && mapped instanceof MappingTreeView.MemberMappingView) { + String descriptor = ((MappingTreeView.MemberMappingView) mapped).getSrcDesc(); + + if (descriptor != null && !descriptor.isEmpty()) { + builder.append(' '); + builder.append(descriptor); + } + } + } + + builder.append('\n'); + } + + private static void writeHeader(List<String> namespaces, StringBuilder builder) { + builder.append("tsrg2"); + + for (String namespace : namespaces) { + builder.append(' '); + builder.append(namespace); + } + + builder.append('\n'); + } +} |