aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/forgeInject/java/mcp/MethodsReturnNonnullByDefault.java34
-rw-r--r--src/forgeInject/java/net/fabricmc/loom/inject/Pair.java52
-rw-r--r--src/forgeInject/java/net/fabricmc/loom/inject/YarnNamingService.java127
-rw-r--r--src/forgeInject/java/net/fabricmc/loom/inject/mixin/ForgeLoomMixinRemapperInjectorService.java93
-rw-r--r--src/forgeInject/java/net/fabricmc/loom/inject/mixin/MixinIntermediaryDevRemapper.java233
-rw-r--r--src/forgeInject/resources/META-INF/services/cpw.mods.modlauncher.api.INameMappingService1
-rw-r--r--src/forgeInject/resources/META-INF/services/cpw.mods.modlauncher.api.ITransformationService1
-rw-r--r--src/main/java/net/fabricmc/loom/LoomGradleExtension.java165
-rw-r--r--src/main/java/net/fabricmc/loom/LoomGradlePlugin.java13
-rw-r--r--src/main/java/net/fabricmc/loom/build/JarRemapper.java26
-rw-r--r--src/main/java/net/fabricmc/loom/build/ModCompileRemapper.java21
-rw-r--r--src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java3
-rw-r--r--src/main/java/net/fabricmc/loom/build/nesting/EmptyNestedJarProvider.java14
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java61
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/DependencyProvider.java2
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java7
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/MavenConfiguration.java17
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/MavenPublication.java23
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/RemapConfiguration.java17
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java7
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java61
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/ide/RunConfigSettings.java12
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/launch/LaunchProviderSettings.java78
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java70
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/processors/MinecraftProcessedProvider.java4
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java56
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/MinecraftProvider.java26
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeProvider.java87
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUniversalProvider.java72
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java105
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java72
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java585
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java76
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java78
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProvider.java117
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/mappings/MojangMappingsDependency.java102
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java154
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/minecraft/assets/MinecraftAssetsProvider.java125
-rw-r--r--src/main/java/net/fabricmc/loom/decompilers/fernflower/AbstractFernFlowerDecompiler.java3
-rw-r--r--src/main/java/net/fabricmc/loom/decompilers/fernflower/ForkingJavaExec.java1
-rw-r--r--src/main/java/net/fabricmc/loom/task/AbstractRunTask.java1
-rw-r--r--src/main/java/net/fabricmc/loom/task/GenEclipseRunsTask.java1
-rw-r--r--src/main/java/net/fabricmc/loom/task/GenVsCodeProjectTask.java118
-rw-r--r--src/main/java/net/fabricmc/loom/task/RemapJarTask.java219
-rw-r--r--src/main/java/net/fabricmc/loom/task/RemapSourcesJarTask.java20
-rw-r--r--src/main/java/net/fabricmc/loom/task/RunDataTask.java38
-rw-r--r--src/main/java/net/fabricmc/loom/util/Checksum.java11
-rw-r--r--src/main/java/net/fabricmc/loom/util/Constants.java17
-rw-r--r--src/main/java/net/fabricmc/loom/util/DownloadUtil.java12
-rw-r--r--src/main/java/net/fabricmc/loom/util/FileSystemUtil.java85
-rw-r--r--src/main/java/net/fabricmc/loom/util/HashedDownloadUtil.java42
-rw-r--r--src/main/java/net/fabricmc/loom/util/JarUtil.java50
-rw-r--r--src/main/java/net/fabricmc/loom/util/LoggerFilter.java49
-rw-r--r--src/main/java/net/fabricmc/loom/util/SourceRemapper.java48
-rw-r--r--src/main/java/net/fabricmc/loom/util/ThreadingUtils.java124
-rw-r--r--src/main/java/net/fabricmc/loom/util/function/CollectionUtil.java75
-rw-r--r--src/main/java/net/fabricmc/loom/util/function/FsPathConsumer.java39
-rw-r--r--src/main/java/net/fabricmc/loom/util/function/IoConsumer.java38
-rw-r--r--src/main/java/net/fabricmc/loom/util/function/LazyBool.java52
-rw-r--r--src/main/java/net/fabricmc/loom/util/srg/AtRemapper.java134
-rw-r--r--src/main/java/net/fabricmc/loom/util/srg/CoreModClassRemapper.java116
-rw-r--r--src/main/java/net/fabricmc/loom/util/srg/InnerClassRemapper.java78
-rw-r--r--src/main/java/net/fabricmc/loom/util/srg/MCPReader.java347
-rw-r--r--src/main/java/net/fabricmc/loom/util/srg/MappingException.java36
-rw-r--r--src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java108
-rw-r--r--src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java181
-rw-r--r--src/main/java/net/fabricmc/loom/util/srg/SrgNamedWriter.java47
-rw-r--r--src/main/resources/idea_run_config_template.xml1
-rw-r--r--src/main/resources/log4j2.fabric.xml1
-rw-r--r--src/test/groovy/net/fabricmc/loom/GroovyXmlUtilTest.groovy74
70 files changed, 4770 insertions, 193 deletions
diff --git a/src/forgeInject/java/mcp/MethodsReturnNonnullByDefault.java b/src/forgeInject/java/mcp/MethodsReturnNonnullByDefault.java
new file mode 100644
index 00000000..8fbac050
--- /dev/null
+++ b/src/forgeInject/java/mcp/MethodsReturnNonnullByDefault.java
@@ -0,0 +1,34 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2016, 2017, 2018 FabricMC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package mcp;
+
+/**
+ * A dummy class, required for some Forge classes to load.
+ *
+ * @deprecated Don't use this in your mods. JetBrains annotations are there for you.
+ */
+@Deprecated
+public @interface MethodsReturnNonnullByDefault {
+}
diff --git a/src/forgeInject/java/net/fabricmc/loom/inject/Pair.java b/src/forgeInject/java/net/fabricmc/loom/inject/Pair.java
new file mode 100644
index 00000000..0206cb5d
--- /dev/null
+++ b/src/forgeInject/java/net/fabricmc/loom/inject/Pair.java
@@ -0,0 +1,52 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2016, 2017, 2018 FabricMC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package net.fabricmc.loom.inject;
+
+import java.util.Map;
+
+final class Pair<A, B> implements Map.Entry<A, B> {
+ private final A first;
+ private final B second;
+
+ Pair(A first, B second) {
+ this.first = first;
+ this.second = second;
+ }
+
+ @Override
+ public A getKey() {
+ return first;
+ }
+
+ @Override
+ public B getValue() {
+ return second;
+ }
+
+ @Override
+ public B setValue(B value) {
+ throw new UnsupportedOperationException("Pairs are immutable!");
+ }
+}
diff --git a/src/forgeInject/java/net/fabricmc/loom/inject/YarnNamingService.java b/src/forgeInject/java/net/fabricmc/loom/inject/YarnNamingService.java
new file mode 100644
index 00000000..2b62dc53
--- /dev/null
+++ b/src/forgeInject/java/net/fabricmc/loom/inject/YarnNamingService.java
@@ -0,0 +1,127 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2016, 2017, 2018 FabricMC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package net.fabricmc.loom.inject;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.BiFunction;
+import java.util.function.Predicate;
+
+import cpw.mods.modlauncher.api.INameMappingService;
+
+import net.fabricmc.mapping.tree.TinyMappingFactory;
+import net.fabricmc.mapping.tree.TinyTree;
+
+public class YarnNamingService implements INameMappingService {
+ private static final String PATH_TO_MAPPINGS = "fabric.yarnWithSrg.path";
+ private TinyTree mappings = null;
+
+ @Override
+ public String mappingName() {
+ return "srgtoyarn";
+ }
+
+ @Override
+ public String mappingVersion() {
+ return "1";
+ }
+
+ @Override
+ public Map.Entry<String, String> understanding() {
+ return new Pair<>("srg", "mcp");
+ }
+
+ @Override
+ public BiFunction<Domain, String, String> namingFunction() {
+ return this::remap;
+ }
+
+ private TinyTree getMappings() {
+ if (mappings != null) {
+ return mappings;
+ }
+
+ String pathStr = System.getProperty(PATH_TO_MAPPINGS);
+ if (pathStr == null) throw new RuntimeException("Missing system property '" + PATH_TO_MAPPINGS + "'!");
+ Path path = Paths.get(pathStr);
+
+ try (BufferedReader reader = Files.newBufferedReader(path)) {
+ mappings = TinyMappingFactory.loadWithDetection(reader);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+
+ return mappings;
+ }
+
+ private String remap(Domain domain, String name) {
+ TinyTree mappings = getMappings();
+
+ switch (domain) {
+ case CLASS:
+ boolean dot = name.contains(".");
+ return find(mappings.getClasses(), def -> maybeReplace(dot, def.getName("srg"), '/', '.').equals(name))
+ .map(def -> maybeReplace(dot, def.getName("named"), '/', '.'))
+ .orElse(name);
+ case METHOD:
+ return mappings.getClasses().stream()
+ .flatMap(def -> def.getMethods().stream())
+ .filter(def -> def.getName("srg").equals(name))
+ .findAny()
+ .map(def -> def.getName("named"))
+ .orElse(name);
+ case FIELD:
+ return mappings.getClasses().stream()
+ .flatMap(def -> def.getFields().stream())
+ .filter(def -> def.getName("srg").equals(name))
+ .findAny()
+ .map(def -> def.getName("named"))
+ .orElse(name);
+ default:
+ return name;
+ }
+ }
+
+ // From CollectionUtil
+ private 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();
+ }
+
+ private static String maybeReplace(boolean run, String s, char from, char to) {
+ return run ? s.replace(from, to) : s;
+ }
+}
diff --git a/src/forgeInject/java/net/fabricmc/loom/inject/mixin/ForgeLoomMixinRemapperInjectorService.java b/src/forgeInject/java/net/fabricmc/loom/inject/mixin/ForgeLoomMixinRemapperInjectorService.java
new file mode 100644
index 00000000..1fe961c6
--- /dev/null
+++ b/src/forgeInject/java/net/fabricmc/loom/inject/mixin/ForgeLoomMixinRemapperInjectorService.java
@@ -0,0 +1,93 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2016, 2017, 2018 FabricMC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package net.fabricmc.loom.inject.mixin;
+
+import java.io.BufferedReader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import cpw.mods.modlauncher.api.IEnvironment;
+import cpw.mods.modlauncher.api.ITransformationService;
+import cpw.mods.modlauncher.api.ITransformer;
+import cpw.mods.modlauncher.api.IncompatibleEnvironmentException;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.spongepowered.asm.mixin.MixinEnvironment;
+
+import net.fabricmc.mapping.tree.TinyMappingFactory;
+import net.fabricmc.mapping.tree.TinyTree;
+
+public class ForgeLoomMixinRemapperInjectorService implements ITransformationService {
+ private static final Logger LOGGER = LogManager.getLogger("ForgeLoomRemapperInjector");
+
+ @Override
+ public String name() {
+ return "ForgeLoomMixinRemapperInjector";
+ }
+
+ @Override
+ public void initialize(IEnvironment environment) {
+ }
+
+ @Override
+ public void beginScanning(IEnvironment environment) {
+ LOGGER.debug("We will be injecting our remapper.");
+
+ try {
+ MixinEnvironment.getDefaultEnvironment().getRemappers().add(new MixinIntermediaryDevRemapper(Objects.requireNonNull(resolveMappings()), "intermediary", "named"));
+ LOGGER.debug("We have successfully injected our remapper.");
+ } catch (Exception e) {
+ LOGGER.debug("We have failed to inject our remapper.", e);
+ }
+ }
+
+ @Override
+ public void onLoad(IEnvironment env, Set<String> otherServices) throws IncompatibleEnvironmentException {
+ }
+
+ @Override
+ public List<ITransformer> transformers() {
+ return Collections.emptyList();
+ }
+
+ private static TinyTree resolveMappings() {
+ try {
+ String srgNamedProperty = System.getProperty("mixin.forgeloom.inject.mappings.srg-named");
+ Path path = Paths.get(srgNamedProperty);
+
+ try (BufferedReader reader = Files.newBufferedReader(path)) {
+ return TinyMappingFactory.loadWithDetection(reader);
+ }
+ } catch (Throwable throwable) {
+ throwable.printStackTrace();
+ return null;
+ }
+ }
+}
diff --git a/src/forgeInject/java/net/fabricmc/loom/inject/mixin/MixinIntermediaryDevRemapper.java b/src/forgeInject/java/net/fabricmc/loom/inject/mixin/MixinIntermediaryDevRemapper.java
new file mode 100644
index 00000000..ef5724dc
--- /dev/null
+++ b/src/forgeInject/java/net/fabricmc/loom/inject/mixin/MixinIntermediaryDevRemapper.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright 2016 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.loom.inject.mixin;
+
+import java.util.ArrayDeque;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+
+import org.spongepowered.asm.mixin.transformer.ClassInfo;
+
+import net.fabricmc.mapping.tree.ClassDef;
+import net.fabricmc.mapping.tree.Descriptored;
+import net.fabricmc.mapping.tree.TinyTree;
+import net.fabricmc.mapping.util.MixinRemapper;
+
+public class MixinIntermediaryDevRemapper extends MixinRemapper {
+ private static final String ambiguousName = "<ambiguous>"; // dummy value for ambiguous mappings - needs querying with additional owner and/or desc info
+
+ private final Set<String> allPossibleClassNames = new HashSet<>();
+ private final Map<String, String> nameFieldLookup = new HashMap<>();
+ private final Map<String, String> nameMethodLookup = new HashMap<>();
+ private final Map<String, String> nameDescFieldLookup = new HashMap<>();
+ private final Map<String, String> nameDescMethodLookup = new HashMap<>();
+
+ public MixinIntermediaryDevRemapper(TinyTree mappings, String from, String to) {
+ super(mappings, from, to);
+
+ for (ClassDef classDef : mappings.getClasses()) {
+ allPossibleClassNames.add(classDef.getName(from));
+ allPossibleClassNames.add(classDef.getName(to));
+
+ putMemberInLookup(from, to, classDef.getFields(), nameFieldLookup, nameDescFieldLookup);
+ putMemberInLookup(from, to, classDef.getMethods(), nameMethodLookup, nameDescMethodLookup);
+ }
+ }
+
+ private <T extends Descriptored> void putMemberInLookup(String from, String to, Collection<T> descriptored, Map<String, String> nameMap, Map<String, String> nameDescMap) {
+ for (T field : descriptored) {
+ String nameFrom = field.getName(from);
+ String descFrom = field.getDescriptor(from);
+ String nameTo = field.getName(to);
+
+ String prev = nameMap.putIfAbsent(nameFrom, nameTo);
+
+ if (prev != null && prev != ambiguousName && !prev.equals(nameTo)) {
+ nameDescMap.put(nameFrom, ambiguousName);
+ }
+
+ String key = getNameDescKey(nameFrom, descFrom);
+ prev = nameDescMap.putIfAbsent(key, nameTo);
+
+ if (prev != null && prev != ambiguousName && !prev.equals(nameTo)) {
+ nameDescMap.put(key, ambiguousName);
+ }
+ }
+ }
+
+ private void throwAmbiguousLookup(String type, String name, String desc) {
+ throw new RuntimeException("Ambiguous Mixin: " + type + " lookup " + name + " " + desc + " is not unique");
+ }
+
+ private String mapMethodNameInner(String owner, String name, String desc) {
+ String result = super.mapMethodName(owner, name, desc);
+
+ if (result.equals(name)) {
+ String otherClass = unmap(owner);
+ return super.mapMethodName(otherClass, name, unmapDesc(desc));
+ } else {
+ return result;
+ }
+ }
+
+ private String mapFieldNameInner(String owner, String name, String desc) {
+ String result = super.mapFieldName(owner, name, desc);
+
+ if (result.equals(name)) {
+ String otherClass = unmap(owner);
+ return super.mapFieldName(otherClass, name, unmapDesc(desc));
+ } else {
+ return result;
+ }
+ }
+
+ @Override
+ public String mapMethodName(String owner, String name, String desc) {
+ // handle unambiguous values early
+ if (owner == null || allPossibleClassNames.contains(owner)) {
+ String newName;
+
+ if (desc == null) {
+ newName = nameMethodLookup.get(name);
+ } else {
+ newName = nameDescMethodLookup.get(getNameDescKey(name, desc));
+ }
+
+ if (newName != null) {
+ if (newName == ambiguousName) {
+ if (owner == null) {
+ throwAmbiguousLookup("method", name, desc);
+ }
+ } else {
+ return newName;
+ }
+ } else if (owner == null) {
+ return name;
+ } else {
+ // FIXME: this kind of namespace mixing shouldn't happen..
+ // TODO: this should not repeat more than once
+ String unmapOwner = unmap(owner);
+ String unmapDesc = unmapDesc(desc);
+
+ if (!unmapOwner.equals(owner) || !unmapDesc.equals(desc)) {
+ return mapMethodName(unmapOwner, name, unmapDesc);
+ } else {
+ // take advantage of the fact allPossibleClassNames
+ // and nameDescLookup cover all sets; if none are present,
+ // we don't have a mapping for it.
+ return name;
+ }
+ }
+ }
+
+ Queue<ClassInfo> classInfos = new ArrayDeque<>();
+ classInfos.add(ClassInfo.forName(owner));
+
+ while (!classInfos.isEmpty()) {
+ ClassInfo c = classInfos.remove();
+ String ownerO = unmap(c.getName());
+ String s;
+
+ if (!(s = mapMethodNameInner(ownerO, name, desc)).equals(name)) {
+ return s;
+ }
+
+ if (!c.getSuperName().startsWith("java/")) {
+ ClassInfo cSuper = c.getSuperClass();
+
+ if (cSuper != null) {
+ classInfos.add(cSuper);
+ }
+ }
+
+ for (String itf : c.getInterfaces()) {
+ if (itf.startsWith("java/")) {
+ continue;
+ }
+
+ ClassInfo cItf = ClassInfo.forName(itf);
+
+ if (cItf != null) {
+ classInfos.add(cItf);
+ }
+ }
+ }
+
+ return name;
+ }
+
+ @Override
+ public String mapFieldName(String owner, String name, String desc) {
+ // handle unambiguous values early
+ if (owner == null || allPossibleClassNames.contains(owner)) {
+ String newName = nameDescFieldLookup.get(getNameDescKey(name, desc));
+
+ if (newName != null) {
+ if (newName == ambiguousName) {
+ if (owner == null) {
+ throwAmbiguousLookup("field", name, desc);
+ }
+ } else {
+ return newName;
+ }
+ } else if (owner == null) {
+ return name;
+ } else {
+ // FIXME: this kind of namespace mixing shouldn't happen..
+ // TODO: this should not repeat more than once
+ String unmapOwner = unmap(owner);
+ String unmapDesc = unmapDesc(desc);
+
+ if (!unmapOwner.equals(owner) || !unmapDesc.equals(desc)) {
+ return mapFieldName(unmapOwner, name, unmapDesc);
+ } else {
+ // take advantage of the fact allPossibleClassNames
+ // and nameDescLookup cover all sets; if none are present,
+ // we don't have a mapping for it.
+ return name;
+ }
+ }
+ }
+
+ ClassInfo c = ClassInfo.forName(map(owner));
+
+ while (c != null) {
+ String nextOwner = unmap(c.getName());
+ String s;
+
+ if (!(s = mapFieldNameInner(nextOwner, name, desc)).equals(name)) {
+ return s;
+ }
+
+ if (c.getSuperName().startsWith("java/")) {
+ break;
+ }
+
+ c = c.getSuperClass();
+ }
+
+ return name;
+ }
+
+ private static String getNameDescKey(String name, String descriptor) {
+ return name + ";;" + descriptor;
+ }
+}
diff --git a/src/forgeInject/resources/META-INF/services/cpw.mods.modlauncher.api.INameMappingService b/src/forgeInject/resources/META-INF/services/cpw.mods.modlauncher.api.INameMappingService
new file mode 100644
index 00000000..45290566
--- /dev/null
+++ b/src/forgeInject/resources/META-INF/services/cpw.mods.modlauncher.api.INameMappingService
@@ -0,0 +1 @@
+net.fabricmc.loom.inject.YarnNamingService
diff --git a/src/forgeInject/resources/META-INF/services/cpw.mods.modlauncher.api.ITransformationService b/src/forgeInject/resources/META-INF/services/cpw.mods.modlauncher.api.ITransformationService
new file mode 100644
index 00000000..0fb04144
--- /dev/null
+++ b/src/forgeInject/resources/META-INF/services/cpw.mods.modlauncher.api.ITransformationService
@@ -0,0 +1 @@
+net.fabricmc.loom.inject.mixin.ForgeLoomMixinRemapperInjectorService \ No newline at end of file
diff --git a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java
index 3234a628..967921f1 100644
--- a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java
+++ b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java
@@ -27,10 +27,13 @@ package net.fabricmc.loom;
import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
+import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@@ -44,19 +47,33 @@ import org.gradle.api.Project;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.plugins.BasePluginConvention;
+import org.gradle.api.plugins.JavaPluginConvention;
+import org.gradle.api.tasks.SourceSet;
import org.jetbrains.annotations.ApiStatus;
import net.fabricmc.loom.api.decompilers.LoomDecompiler;
import net.fabricmc.loom.configuration.LoomDependencyManager;
+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.configuration.processors.JarProcessorManager;
import net.fabricmc.loom.configuration.providers.MinecraftProvider;
+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.MappingsProvider;
import net.fabricmc.loom.configuration.providers.mappings.MojangMappingsDependency;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider;
+import net.fabricmc.loom.util.function.LazyBool;
public class LoomGradleExtension {
+ private static final String FORGE_PROPERTY = "loom.forge";
+ private static final String INCLUDE_PROPERTY = "loom.forge.include";
+
public String refmapName;
public String loaderLaunchMethod;
public boolean remapMod = true;
@@ -64,23 +81,40 @@ public class LoomGradleExtension {
public File accessWidener = null;
public Function<String, Object> intermediaryUrl = mcVer -> "https://maven.fabricmc.net/net/fabricmc/intermediary/" + mcVer + "/intermediary-" + mcVer + "-v2.jar";
public boolean shareCaches = false;
+ public List<String> mixinConfigs = new ArrayList<>(); // FORGE: Passed to Minecraft
+ public boolean useFabricMixin = true; // FORGE: Use Fabric Mixin for better refmap resolutions
private final ConfigurableFileCollection unmappedMods;
private final ConfigurableFileCollection log4jConfigs;
final List<LoomDecompiler> decompilers = new ArrayList<>();
private final List<JarProcessor> jarProcessors = new ArrayList<>();
+ private boolean silentMojangMappingsLicense = false;
+ public Boolean generateSrgTiny = null;
// Not to be set in the build.gradle
private final Project project;
+ private List<String> dataGenMods = new ArrayList<>();
private LoomDependencyManager dependencyManager;
private JarProcessorManager jarProcessorManager;
private JsonObject installerJson;
private MappingSet[] srcMappingCache = new MappingSet[2];
private Mercury[] srcMercuryCache = new Mercury[2];
+ private final LazyBool forge;
+ private final LazyBool supportsInclude;
private Set<File> mixinMappings = Collections.synchronizedSet(new HashSet<>());
+ private final List<String> tasksBeforeRun = Collections.synchronizedList(new ArrayList<>());
+ public final List<Supplier<SourceSet>> forgeLocalMods = Collections.synchronizedList(new ArrayList<>(Arrays.asList(new Supplier<SourceSet>() {
+ @Override
+ public SourceSet get() {
+ return project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets().getByName("main");
+ }
+ })));
+ @ApiStatus.Experimental
+ public final List<Consumer<RunConfig>> settingsPostEdit = new ArrayList<>();
private NamedDomainObjectContainer<RunConfigSettings> runConfigs;
+ private NamedDomainObjectContainer<LaunchProviderSettings> launchConfigs;
/**
* Loom will generate a new genSources task (with a new name, based off of {@link LoomDecompiler#name()})
@@ -102,13 +136,81 @@ public class LoomGradleExtension {
}
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());
}
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());
}
+ public void localMods(Action<SourceSetConsumer> action) {
+ if (!isForge()) {
+ throw new UnsupportedOperationException("Not running with Forge support.");
+ }
+
+ action.execute(new SourceSetConsumer());
+ }
+
+ public boolean isDataGenEnabled() {
+ return isForge() && !dataGenMods.isEmpty();
+ }
+
+ public List<String> getDataGenMods() {
+ return dataGenMods;
+ }
+
+ public class SourceSetConsumer {
+ public void add(Object... sourceSets) {
+ for (Object sourceSet : sourceSets) {
+ if (sourceSet instanceof SourceSet) {
+ forgeLocalMods.add(() -> (SourceSet) sourceSet);
+ } else {
+ forgeLocalMods.add(() -> project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets().findByName(String.valueOf(forgeLocalMods)));
+ }
+ }
+ }
+ }
+
+ public void dataGen(Action<DataGenConsumer> action) {
+ if (!isForge()) {
+ throw new UnsupportedOperationException("Not running with Forge support.");
+ }
+
+ action.execute(new DataGenConsumer());
+ }
+
+ public class DataGenConsumer {
+ public void mod(String... modIds) {
+ dataGenMods.addAll(Arrays.asList(modIds));
+
+ if (modIds.length > 0 && getRunConfigs().findByName("data") == null) {
+ getRunConfigs().create("data", RunConfigSettings::data);
+ }
+ }
+ }
+
+ public void addTaskBeforeRun(String task) {
+ this.tasksBeforeRun.add(task);
+ }
+
+ public List<String> getTasksBeforeRun() {
+ return tasksBeforeRun;
+ }
+
+ public void mixinConfig(String... config) {
+ mixinConfigs.addAll(Arrays.asList(config));
+ }
+
+ public void silentMojangMappingsLicense() {
+ this.silentMojangMappingsLicense = true;
+ }
+
+ public boolean isSilentMojangMappingsLicenseEnabled() {
+ return silentMojangMappingsLicense;
+ }
+
public Dependency officialMojangMappings() {
return new MojangMappingsDependency(project, this);
}
@@ -116,8 +218,12 @@ public class LoomGradleExtension {
public LoomGradleExtension(Project project) {
this.project = project;
this.unmappedMods = project.files();
+ this.forge = new LazyBool(() -> Boolean.parseBoolean(Objects.toString(project.findProperty(FORGE_PROPERTY))));
+ this.supportsInclude = new LazyBool(() -> Boolean.parseBoolean(Objects.toString(project.findProperty(INCLUDE_PROPERTY))));
this.runConfigs = project.container(RunConfigSettings.class,
baseName -> new RunConfigSettings(project, baseName));
+ this.launchConfigs = project.container(LaunchProviderSettings.class,
+ baseName -> new LaunchProviderSettings(project, baseName));
this.log4jConfigs = project.files(getDefaultLog4jConfigFile());
}
@@ -266,6 +372,10 @@ public class LoomGradleExtension {
return dependencyManager;
}
+ public PatchProvider getPatchProvider() {
+ return getDependencyManager().getProvider(PatchProvider.class);
+ }
+
public MinecraftProvider getMinecraftProvider() {
return getDependencyManager().getProvider(MinecraftProvider.class);
}
@@ -278,6 +388,26 @@ public class LoomGradleExtension {
return getDependencyManager().getProvider(MappingsProvider.class);
}
+ public McpConfigProvider getMcpConfigProvider() {
+ return getDependencyManager().getProvider(McpConfigProvider.class);
+ }
+
+ public SrgProvider getSrgProvider() {
+ return getDependencyManager().getProvider(SrgProvider.class);
+ }
+
+ public ForgeUniversalProvider getForgeUniversalProvider() {
+ return getDependencyManager().getProvider(ForgeUniversalProvider.class);
+ }
+
+ public ForgeUserdevProvider getForgeUserdevProvider() {
+ return getDependencyManager().getProvider(ForgeUserdevProvider.class);
+ }
+
+ public ForgeProvider getForgeProvider() {
+ return getDependencyManager().getProvider(ForgeProvider.class);
+ }
+
public void setDependencyManager(LoomDependencyManager dependencyManager) {
this.dependencyManager = dependencyManager;
}
@@ -296,7 +426,14 @@ public class LoomGradleExtension {
public String getRefmapName() {
if (refmapName == null || refmapName.isEmpty()) {
- String defaultRefmapName = project.getConvention().getPlugin(BasePluginConvention.class).getArchivesBaseName() + "-refmap.json";
+ String defaultRefmapName;
+
+ if (isRootProject()) {
+ defaultRefmapName = project.getConvention().getPlugin(BasePluginConvention.class).getArchivesBaseName() + "-refmap.json";
+ } else {
+ 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);
refmapName = defaultRefmapName;
}
@@ -334,6 +471,22 @@ public class LoomGradleExtension {
return shareCaches;
}
+ public boolean isForge() {
+ return forge.getAsBoolean();
+ }
+
+ public boolean supportsInclude() {
+ return !isForge() || supportsInclude.getAsBoolean();
+ }
+
+ public boolean shouldGenerateSrgTiny() {
+ if (generateSrgTiny != null) {
+ return generateSrgTiny;
+ }
+
+ return isForge();
+ }
+
// Creates a new file each time its called, this is then held onto later when remapping the output jar
// Required as now when using parallel builds the old single file could be written by another sourceset compile task
public synchronized File getNextMixinMappings() {
@@ -368,7 +521,17 @@ public class LoomGradleExtension {
}
@ApiStatus.Experimental
+ public void launches(Action<NamedDomainObjectContainer<LaunchProviderSettings>> action) {
+ action.execute(launchConfigs);
+ }
+
+ @ApiStatus.Experimental
public NamedDomainObjectContainer<RunConfigSettings> getRunConfigs() {
return runConfigs;
}
+
+ @ApiStatus.Experimental
+ public NamedDomainObjectContainer<LaunchProviderSettings> getLaunchConfigs() {
+ return launchConfigs;
+ }
}
diff --git a/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java b/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java
index cac0b324..38c619dc 100644
--- a/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java
+++ b/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java
@@ -24,6 +24,10 @@
package net.fabricmc.loom;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@@ -44,7 +48,14 @@ public class LoomGradlePlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
- project.getLogger().lifecycle("Fabric Loom: " + LoomGradlePlugin.class.getPackage().getImplementationVersion());
+ String loomVersion = LoomGradlePlugin.class.getPackage().getImplementationVersion();
+ Set<String> loggedVersions = new HashSet<>(Arrays.asList(System.getProperty("loom.printed.logged", "").split(",")));
+
+ if (!loggedVersions.contains(loomVersion)) {
+ loggedVersions.add(loomVersion);
+ System.setProperty("loom.printed.logged", String.join(",", loggedVersions));
+ project.getLogger().lifecycle("Architectury Loom: " + loomVersion);
+ }
refreshDeps = project.getGradle().getStartParameter().isRefreshDependencies();
diff --git a/src/main/java/net/fabricmc/loom/build/JarRemapper.java b/src/main/java/net/fabricmc/loom/build/JarRemapper.java
index 17896428..063bb669 100644
--- a/src/main/java/net/fabricmc/loom/build/JarRemapper.java
+++ b/src/main/java/net/fabricmc/loom/build/JarRemapper.java
@@ -35,8 +35,10 @@ import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import org.gradle.api.Action;
+import org.gradle.api.Project;
import org.objectweb.asm.commons.Remapper;
+import net.fabricmc.loom.util.LoggerFilter;
import net.fabricmc.stitch.util.Pair;
import net.fabricmc.tinyremapper.IMappingProvider;
import net.fabricmc.tinyremapper.InputTag;
@@ -63,8 +65,10 @@ 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);
mappingProviders.forEach(remapperBuilder::withMappings);
if (remapOptions != null) {
@@ -77,7 +81,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);
@@ -86,13 +90,27 @@ 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);
+ }
}
List<OutputConsumerPath> outputConsumers = new ArrayList<>();
for (RemapData data : remapData) {
- OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(data.output).build();
+ OutputConsumerPath outputConsumer;
+ project.getLogger().info(":remapper output -> " + data.output.getFileName().toString());
+
+ try {
+ 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/ModCompileRemapper.java b/src/main/java/net/fabricmc/loom/build/ModCompileRemapper.java
index 1abd8662..72cf4620 100644
--- a/src/main/java/net/fabricmc/loom/build/ModCompileRemapper.java
+++ b/src/main/java/net/fabricmc/loom/build/ModCompileRemapper.java
@@ -79,7 +79,7 @@ public class ModCompileRemapper {
String name = artifact.getModuleVersion().getId().getName();
String version = artifact.getModuleVersion().getId().getVersion();
- if (!isFabricMod(logger, artifact)) {
+ if (!shouldRemapMod(logger, artifact, extension.isForge(), sourceConfig.getName())) {
addToRegularCompile(project, regularConfig, artifact);
continue;
}
@@ -92,7 +92,7 @@ public class ModCompileRemapper {
modDependencies.add(info);
- String remappedLog = group + ":" + name + ":" + version + (artifact.getClassifier() == null ? "" : ":" + artifact.getClassifier()) + " (" + mappingsSuffix + ")";
+ String remappedLog = group + ":" + name + ":" + version + (artifact.getClassifier() == null ? "" : ":" + artifact.getClassifier()) + " (" + mappingsSuffix + ")" + (info.requiresRemapping() ? " requires remapping" : " already remapped in " + info.getRemappedOutput().getAbsolutePath());
project.getLogger().info(":providing " + remappedLog);
File remappedSources = info.getRemappedOutput("sources");
@@ -116,6 +116,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());
}
}
@@ -124,13 +125,23 @@ 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, ResolvedArtifact artifact) {
+ private static boolean shouldRemapMod(Logger logger, ResolvedArtifact artifact, boolean forge, String config) {
File input = artifact.getFile();
try (ZipFile zipFile = new ZipFile(input)) {
- if (zipFile.getEntry("fabric.mod.json") != null) {
- logger.info("Found Fabric mod in modCompile: {}", artifact.getId());
+ if (forge) {
+ if (zipFile.getEntry("META-INF/mods.toml") != null) {
+ logger.info("Found Forge mod in " + config + ": {}", artifact.getId());
+ return true;
+ }
+
+ logger.lifecycle(":could not find forge mod in " + config + " but forcing: {}", artifact.getId());
return true;
+ } else {
+ if (zipFile.getEntry("fabric.mod.json") != null) {
+ logger.info("Found Fabric mod in " + config + ": {}", artifact.getId());
+ return true;
+ }
}
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 ecdaf152..6a0a2f65 100644
--- a/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java
+++ b/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java
@@ -71,8 +71,9 @@ public abstract class AnnotationProcessorInvoker<T extends Task> {
private void passMixinArguments(T task) {
try {
LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class);
+ File inMapFile = extension.getMappingsProvider().tinyMappings;
Map<String, String> args = new HashMap<String, String>() {{
- put(Constants.MixinArguments.IN_MAP_FILE_NAMED_INTERMEDIARY, extension.getMappingsProvider().tinyMappings.getCanonicalPath());
+ put(Constants.MixinArguments.IN_MAP_FILE_NAMED_INTERMEDIARY, inMapFile.getCanonicalPath());
put(Constants.MixinArguments.OUT_MAP_FILE_NAMED_INTERMEDIARY, extension.getNextMixinMappings().getCanonicalPath());
put(Constants.MixinArguments.OUT_REFMAP_FILE, getRefmapDestination(task, extension));
put(Constants.MixinArguments.DEFAULT_OBFUSCATION_ENV, "named:intermediary");
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..df9f7cab
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/build/nesting/EmptyNestedJarProvider.java
@@ -0,0 +1,14 @@
+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 45d114e1..a3e7e448 100644
--- a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java
+++ b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java
@@ -30,6 +30,7 @@ import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginConvention;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.bundling.AbstractArchiveTask;
+import org.gradle.api.tasks.bundling.Jar;
import org.gradle.api.tasks.javadoc.Javadoc;
import net.fabricmc.loom.LoomGradleExtension;
@@ -39,7 +40,14 @@ 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.MinecraftProvider;
+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.MappingsProvider;
+import net.fabricmc.loom.task.GenVsCodeProjectTask;
import net.fabricmc.loom.util.Constants;
public final class CompileConfiguration {
@@ -61,8 +69,40 @@ public final class CompileConfiguration {
Configuration minecraftConfig = project.getConfigurations().maybeCreate(Constants.Configurations.MINECRAFT);
minecraftConfig.setTransitive(false);
- Configuration includeConfig = project.getConfigurations().maybeCreate(Constants.Configurations.INCLUDE);
- includeConfig.setTransitive(false); // Dont get transitive deps
+ project.afterEvaluate(project1 -> {
+ if (project.getExtensions().getByType(LoomGradleExtension.class).shouldGenerateSrgTiny()) {
+ Configuration srg = project.getConfigurations().maybeCreate(Constants.Configurations.SRG);
+ srg.setTransitive(false);
+ }
+
+ if (project.getExtensions().getByType(LoomGradleExtension.class).isDataGenEnabled()) {
+ project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets().getByName("main").resources(files -> {
+ files.srcDir(project.file("src/generated/resources"));
+ });
+ }
+ });
+
+ if (project.getExtensions().getByType(LoomGradleExtension.class).isForge()) {
+ Configuration forgeConfig = project.getConfigurations().maybeCreate(Constants.Configurations.FORGE);
+ forgeConfig.setTransitive(false);
+ Configuration forgeUserdevConfig = project.getConfigurations().maybeCreate(Constants.Configurations.FORGE_USERDEV);
+ forgeUserdevConfig.setTransitive(false);
+ Configuration forgeInstallerConfig = project.getConfigurations().maybeCreate(Constants.Configurations.FORGE_INSTALLER);
+ forgeInstallerConfig.setTransitive(false);
+ Configuration forgeUniversalConfig = project.getConfigurations().maybeCreate(Constants.Configurations.FORGE_UNIVERSAL);
+ forgeUniversalConfig.setTransitive(false);
+ Configuration forgeDependencies = project.getConfigurations().maybeCreate(Constants.Configurations.FORGE_DEPENDENCIES);
+ forgeDependencies.setTransitive(false);
+ Configuration mcpConfig = project.getConfigurations().maybeCreate(Constants.Configurations.MCP_CONFIG);
+ mcpConfig.setTransitive(false);
+
+ extendsFrom(Constants.Configurations.MINECRAFT_DEPENDENCIES, Constants.Configurations.FORGE_DEPENDENCIES, project);
+ }
+
+ if (project.getExtensions().getByType(LoomGradleExtension.class).supportsInclude()) {
+ Configuration includeConfig = project.getConfigurations().maybeCreate(Constants.Configurations.INCLUDE);
+ includeConfig.setTransitive(false); // Dont get transitive deps
+ }
project.getConfigurations().maybeCreate(Constants.Configurations.MAPPING_CONSTANTS);
extendsFrom(JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME, Constants.Configurations.MAPPING_CONSTANTS, project);
@@ -117,6 +157,22 @@ public final class CompileConfiguration {
extension.setDependencyManager(dependencyManager);
dependencyManager.addProvider(new MinecraftProvider(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(new MappingsProvider(project));
dependencyManager.addProvider(new LaunchProvider(project));
@@ -127,6 +183,7 @@ public final class CompileConfiguration {
project.getTasks().getByName("cleanEclipse").finalizedBy(project.getTasks().getByName("cleanEclipseRuns"));
SetupIntelijRunConfigs.setup(project);
+ GenVsCodeProjectTask.generate(project);
// Enables the default mod remapper
if (extension.remapMod) {
diff --git a/src/main/java/net/fabricmc/loom/configuration/DependencyProvider.java b/src/main/java/net/fabricmc/loom/configuration/DependencyProvider.java
index 63ef8354..2b971427 100644
--- a/src/main/java/net/fabricmc/loom/configuration/DependencyProvider.java
+++ b/src/main/java/net/fabricmc/loom/configuration/DependencyProvider.java
@@ -190,7 +190,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));
diff --git a/src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java b/src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java
index 397908c5..f610c5ee 100644
--- a/src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java
+++ b/src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java
@@ -130,14 +130,15 @@ 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 mappingsKey = mappingsProvider.getMappingsKey();
+ String platformSuffix = extension.isForge() ? "_forge" : "";
+ String mappingsKey = mappingsProvider.getMappingsKey() + platformSuffix;
if (extension.getInstallerJson() == null) {
//If we've not found the installer JSON we've probably skipped remapping Fabric loader, let's go looking
@@ -160,7 +161,7 @@ public class LoomDependencyManager {
}
}
- if (extension.getInstallerJson() == null) {
+ if (extension.getInstallerJson() == null && !extension.isForge()) {
project.getLogger().warn("fabric-installer.json not found in classpath!");
}
diff --git a/src/main/java/net/fabricmc/loom/configuration/MavenConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/MavenConfiguration.java
index 2fb2ae7c..118fd546 100644
--- a/src/main/java/net/fabricmc/loom/configuration/MavenConfiguration.java
+++ b/src/main/java/net/fabricmc/loom/configuration/MavenConfiguration.java
@@ -25,6 +25,7 @@
package net.fabricmc.loom.configuration;
import org.gradle.api.Project;
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
import net.fabricmc.loom.LoomGradleExtension;
@@ -52,6 +53,22 @@ public class MavenConfiguration {
repo.setUrl("https://libraries.minecraft.net/");
});
+ project.getRepositories().maven(repo -> {
+ repo.setName("Forge");
+ repo.setUrl("https://files.minecraftforge.net/maven/");
+
+ repo.metadataSources(sources -> {
+ sources.mavenPom();
+
+ try {
+ MavenArtifactRepository.MetadataSources.class.getDeclaredMethod("ignoreGradleMetadataRedirection")
+ .invoke(sources);
+ } catch (Throwable ignored) {
+ // Method not available
+ }
+ });
+ });
+
project.getRepositories().mavenCentral();
}
}
diff --git a/src/main/java/net/fabricmc/loom/configuration/MavenPublication.java b/src/main/java/net/fabricmc/loom/configuration/MavenPublication.java
index 92acad95..16cc4560 100644
--- a/src/main/java/net/fabricmc/loom/configuration/MavenPublication.java
+++ b/src/main/java/net/fabricmc/loom/configuration/MavenPublication.java
@@ -34,6 +34,7 @@ import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.ExcludeRule;
import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.logging.Logger;
import org.gradle.api.publish.Publication;
import org.gradle.api.publish.PublishingExtension;
@@ -46,6 +47,14 @@ public final class MavenPublication {
public static void configure(Project project) {
project.afterEvaluate((p) -> {
+ // add modsCompile to maven-publish
+ PublishingExtension mavenPublish = p.getExtensions().findByType(PublishingExtension.class);
+
+ if (mavenPublish == null) {
+ p.getLogger().info("No maven publications for project [" + p.getName() + "], skipping configuration.");
+ return;
+ }
+
for (RemappedConfigurationEntry entry : Constants.MOD_COMPILE_ENTRIES) {
if (!entry.hasMavenScope()) {
continue;
@@ -53,23 +62,20 @@ public final class MavenPublication {
Configuration compileModsConfig = p.getConfigurations().getByName(entry.getSourceConfiguration());
- // add modsCompile to maven-publish
- PublishingExtension mavenPublish = p.getExtensions().findByType(PublishingExtension.class);
-
- if (mavenPublish != null) {
- processEntry(entry, compileModsConfig, mavenPublish);
- }
+ p.getLogger().info("Processing maven publication for project [" + p.getName() + "] of " + entry.getSourceConfiguration());
+ processEntry(p.getLogger(), entry, compileModsConfig, mavenPublish);
}
});
}
- private static void processEntry(RemappedConfigurationEntry entry, Configuration compileModsConfig, PublishingExtension mavenPublish) {
+ private static void processEntry(Logger logger, RemappedConfigurationEntry entry, Configuration compileModsConfig, PublishingExtension mavenPublish) {
mavenPublish.publications((publications) -> {
for (Publication publication : publications) {
if (!(publication instanceof org.gradle.api.publish.maven.MavenPublication)) {
continue;
}
+ logger.info("Processing maven publication [" + publication.getName() + "]");
((org.gradle.api.publish.maven.MavenPublication) publication).pom((pom) -> pom.withXml((xml) -> {
Node dependencies = GroovyXmlUtil.getOrCreateNode(xml.asNode(), "dependencies");
Set<String> foundArtifacts = new HashSet<>();
@@ -85,9 +91,12 @@ public final class MavenPublication {
for (Dependency dependency : compileModsConfig.getAllDependencies()) {
if (foundArtifacts.contains(dependency.getGroup() + ":" + dependency.getName())) {
+ logger.info("Found inserted artifact " + dependency.getGroup() + ":" + dependency.getName());
continue;
}
+ logger.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 7534c27b..7e1a1f3f 100644
--- a/src/main/java/net/fabricmc/loom/configuration/RemapConfiguration.java
+++ b/src/main/java/net/fabricmc/loom/configuration/RemapConfiguration.java
@@ -27,11 +27,13 @@ package net.fabricmc.loom.configuration;
import java.io.IOException;
import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.UnknownTaskException;
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;
@@ -74,6 +76,12 @@ public class RemapConfiguration {
remapJarTask.getInput().set(jarTask.getArchivePath());
}
+ if (extension.isForge()) {
+ ((Jar) jarTask).manifest(manifest -> {
+ manifest.attributes(ImmutableMap.of("MixinConfigs", String.join(",", extension.mixinConfigs)));
+ });
+ }
+
if (isDefaultRemap) {
extension.getUnmappedModCollection().from(jarTask);
remapJarTask.getAddNestedDependencies().set(true);
@@ -87,7 +95,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);
}
});
@@ -115,7 +123,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);
}
@@ -139,9 +147,10 @@ public class RemapConfiguration {
RemapSourcesJarTask remapSourcesJarTask = (RemapSourcesJarTask) project.getTasks().findByName(remapSourcesJarTaskName);
Preconditions.checkNotNull(remapSourcesJarTask, "Could not find " + remapSourcesJarTaskName + " in " + project.getName());
- remapSourcesJarTask.setInput(sourcesTask.getArchivePath());
remapSourcesJarTask.setOutput(sourcesTask.getArchivePath());
- remapSourcesJarTask.dependsOn(project.getTasks().getByName(sourcesJarTaskName));
+ sourcesTask.setClassifier(sourcesTask.getClassifier() == null ? "dev" : sourcesTask.getClassifier() + "-dev");
+ remapSourcesJarTask.setInput(sourcesTask.getArchivePath());
+ remapSourcesJarTask.dependsOn(sourcesTask);
if (isDefaultRemap) {
remapSourcesJarTask.doLast(task -> project.getArtifacts().add("archives", remapSourcesJarTask.getOutput()));
diff --git a/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java b/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java
index 936e6b7a..42f0e7bf 100644
--- a/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java
+++ b/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java
@@ -55,6 +55,7 @@ import net.fabricmc.accesswidener.AccessWidenerVisitor;
import net.fabricmc.accesswidener.AccessWidenerWriter;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.processors.JarProcessor;
+import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider;
import net.fabricmc.loom.util.Checksum;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.tinyremapper.TinyRemapper;
@@ -94,8 +95,10 @@ public class AccessWidenerJarProcessor implements JarProcessor {
throw new UnsupportedOperationException(String.format("Access Widener namespace '%s' is not a valid namespace, it must be one of: '%s'", accessWidener.getNamespace(), String.join(", ", validNamespaces)));
}
- TinyRemapper tinyRemapper = loomGradleExtension.getMinecraftMappedProvider().getTinyRemapper("official", "named");
- tinyRemapper.readClassPath(loomGradleExtension.getMinecraftMappedProvider().getRemapClasspath());
+ TinyRemapper tinyRemapper = loomGradleExtension.getMinecraftMappedProvider().getTinyRemapper();
+ tinyRemapper.replaceMappings(loomGradleExtension.getMinecraftMappedProvider().getMappings(null, "official", "named"));
+ loomGradleExtension.getMinecraftMappedProvider();
+ tinyRemapper.readClassPath(MinecraftMappedProvider.getRemapClasspath(project));
AccessWidenerRemapper remapper = new AccessWidenerRemapper(accessWidener, tinyRemapper.getRemapper(), "named");
accessWidener = remapper.remap();
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 2fc79906..603de2db 100644
--- a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java
+++ b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java
@@ -24,12 +24,21 @@
package net.fabricmc.loom.configuration.ide;
+import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.UUID;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
@@ -52,11 +61,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 String vmArgs;
public String programArgs;
+ public List<String> vscodeBeforeRun = new ArrayList<>();
+ public final Map<String, String> envVariables = new HashMap<>();
public SourceSet sourceSet;
public Element genRuns(Element doc) {
@@ -75,6 +87,14 @@ public class RunConfig {
this.addXml(root, "option", ImmutableMap.of("name", "PROGRAM_PARAMETERS", "value", 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;
}
@@ -108,6 +128,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() ? "" : project.getPath();
runConfig.vmArgs = "";
runConfig.programArgs = "";
@@ -119,6 +140,23 @@ public class RunConfig {
runConfig.vmArgs = "-Dfabric.dli.config=" + encodeEscaped(extension.getDevLauncherConfig().getAbsolutePath()) + " -Dfabric.dli.env=" + environment.toLowerCase();
}
+ if (extension.isForge()) {
+ List<String> modClasses = new ArrayList<>();
+
+ for (Supplier<SourceSet> sourceSetSupplier : extension.forgeLocalMods) {
+ SourceSet sourceSet = sourceSetSupplier.get();
+ String sourceSetName = sourceSet.getName() + "_" + UUID.randomUUID().toString().replace("-", "").substring(0, 7);
+
+ Stream.concat(
+ Stream.of(sourceSet.getOutput().getResourcesDir().getAbsolutePath()),
+ StreamSupport.stream(sourceSet.getOutput().getClassesDirs().spliterator(), false)
+ .map(File::getAbsolutePath)
+ ).map(s -> sourceSetName + "%%" + s).collect(Collectors.toCollection(() -> modClasses));
+ }
+
+ runConfig.envVariables.put("MOD_CLASSES", String.join(File.pathSeparator, modClasses));
+ }
+
if (extension.getLoaderLaunchMethod().equals("launchwrapper")) {
// if installer.json found...
JsonObject installerJson = extension.getInstallerJson();
@@ -215,6 +253,10 @@ public class RunConfig {
runConfig.programArgs = runConfig.programArgs.trim();
runConfig.vmArgs = runConfig.vmArgs.trim();
+ for (Consumer<RunConfig> consumer : extension.settingsPostEdit) {
+ consumer.accept(runConfig);
+ }
+
return runConfig;
}
@@ -233,6 +275,25 @@ public class RunConfig {
dummyConfig = dummyConfig.replace("%PROGRAM_ARGS%", programArgs.replaceAll("\"", "&quot;"));
dummyConfig = dummyConfig.replace("%VM_ARGS%", vmArgs.replaceAll("\"", "&quot;"));
+ 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("\"", "&quot;"));
+ builder.append("\" value=\"");
+ builder.append(env.getValue().replaceAll("\"", "&quot;"));
+ builder.append("\"/>");
+ }
+
+ builder.append("</envs>");
+ envs = builder.toString();
+ }
+
+ dummyConfig = dummyConfig.replace("%ENVS%", envs);
+
return dummyConfig;
}
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 eb17466d..bfb62ae9 100644
--- a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfigSettings.java
+++ b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfigSettings.java
@@ -267,7 +267,7 @@ public final class RunConfigSettings implements Named {
public void client() {
startFirstThread();
environment("client");
- defaultMainClass(Constants.Knot.KNOT_CLIENT);
+ defaultMainClass(getExtension().isForge() ? Constants.ForgeUserDev.LAUNCH_TESTING : Constants.Knot.KNOT_CLIENT);
}
/**
@@ -276,7 +276,15 @@ public final class RunConfigSettings implements Named {
public void server() {
programArg("nogui");
environment("server");
- defaultMainClass(Constants.Knot.KNOT_SERVER);
+ defaultMainClass(getExtension().isForge() ? Constants.ForgeUserDev.LAUNCH_TESTING : Constants.Knot.KNOT_SERVER);
+ }
+
+ /**
+ * Configure run config with the default server options.
+ */
+ public void data() {
+ environment("data");
+ defaultMainClass(getExtension().isForge() ? Constants.ForgeUserDev.LAUNCH_TESTING : Constants.Knot.KNOT_SERVER);
}
/**
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..985a9db3
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/launch/LaunchProviderSettings.java
@@ -0,0 +1,78 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2016, 2017, 2018 FabricMC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package net.fabricmc.loom.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;
+
+public class LaunchProviderSettings implements Named {
+ private final String name;
+ private List<Map.Entry<String, String>> properties = new ArrayList<>();
+ private List<String> arguments = new ArrayList<>();
+
+ public LaunchProviderSettings(Project project, String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ 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 367a49a6..990c042c 100644
--- a/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java
+++ b/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java
@@ -35,9 +35,13 @@ import java.nio.charset.StandardCharsets;
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.JarFile;
+import java.util.jar.Manifest;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
@@ -60,7 +64,11 @@ import net.fabricmc.loom.configuration.processors.dependency.ModDependencyInfo;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider;
import net.fabricmc.loom.util.Constants;
+import net.fabricmc.loom.util.LoggerFilter;
import net.fabricmc.loom.util.TinyRemapperMappingsHelper;
+import net.fabricmc.loom.util.srg.AtRemapper;
+import net.fabricmc.loom.util.srg.CoreModClassRemapper;
+import net.fabricmc.mapping.tree.TinyTree;
import net.fabricmc.tinyremapper.InputTag;
import net.fabricmc.tinyremapper.OutputConsumerPath;
import net.fabricmc.tinyremapper.TinyRemapper;
@@ -97,6 +105,7 @@ public class ModProcessor {
}
private static void stripNestedJars(File file) {
+ if (!ZipUtil.containsEntry(file, "fabric.mod.json")) return;
// Strip out all contained jar info as we dont want loader to try and load the jars contained in dev.
ZipUtil.transformEntries(file, new ZipEntryTransformerEntry[] {(new ZipEntryTransformerEntry("fabric.mod.json", new StringZipEntryTransformer() {
@Override
@@ -129,24 +138,27 @@ public class ModProcessor {
private static void remapJars(Project project, List<ModDependencyInfo> processList) throws IOException {
LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class);
- String fromM = "intermediary";
+ String fromM = extension.isForge() ? "srg" : "intermediary";
String toM = "named";
MinecraftMappedProvider mappedProvider = extension.getMinecraftMappedProvider();
MappingsProvider mappingsProvider = extension.getMappingsProvider();
- Path mc = mappedProvider.getIntermediaryJar().toPath();
+ Path mc = 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);
+ .stream().map(File::toPath).toArray(Path[]::new);
List<ModDependencyInfo> remapList = processList.stream().filter(ModDependencyInfo::requiresRemapping).collect(Collectors.toList());
project.getLogger().lifecycle(":remapping " + remapList.size() + " mods (TinyRemapper, " + fromM + " -> " + toM + ")");
+ TinyTree mappings = extension.isForge() ? mappingsProvider.getMappingsWithSrg() : mappingsProvider.getMappings();
+ LoggerFilter.replaceSystemOut();
TinyRemapper remapper = TinyRemapper.newRemapper()
- .withMappings(TinyRemapperMappingsHelper.create(mappingsProvider.getMappings(), fromM, toM, false))
- .renameInvalidLocals(false)
- .build();
+ .logger(project.getLogger()::lifecycle)
+ .withMappings(TinyRemapperMappingsHelper.create(mappings, fromM, toM, false))
+ .renameInvalidLocals(false)
+ .build();
remapper.readClassPathAsync(mc);
remapper.readClassPathAsync(mcDeps);
@@ -198,10 +210,56 @@ public class ModProcessor {
ZipUtil.replaceEntry(info.getRemappedOutput(), info.getAccessWidener(), accessWidener);
}
+ if (extension.isForge()) {
+ AtRemapper.remap(project.getLogger(), info.getRemappedOutput().toPath(), mappings);
+ CoreModClassRemapper.remapJar(info.getRemappedOutput().toPath(), mappings, project.getLogger());
+
+ if (ZipUtil.containsEntry(info.getRemappedOutput(), "META-INF/MANIFEST.MF")) {
+ ZipUtil.transformEntry(info.getRemappedOutput(), "META-INF/MANIFEST.MF", (in, zipEntry, out) -> {
+ Manifest manifest = new Manifest(in);
+ fixManifest(manifest);
+ out.putNextEntry(new ZipEntry(zipEntry.getName()));
+ manifest.write(out);
+ out.closeEntry();
+ });
+ }
+
+ List<String> filesToRemove = new ArrayList<>();
+ ZipUtil.iterate(info.getRemappedOutput(), (in, zipEntry) -> {
+ if (zipEntry.getName().toLowerCase(Locale.ROOT).endsWith(".rsa") || zipEntry.getName().toLowerCase(Locale.ROOT).endsWith(".sf")) {
+ if (zipEntry.getName().startsWith("META-INF")) {
+ filesToRemove.add(zipEntry.getName());
+ }
+ }
+ });
+ ZipUtil.removeEntries(info.getRemappedOutput(), filesToRemove.toArray(new String[0]));
+ }
+
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();
+ }
+ }
+
public static JsonObject readInstallerJson(File file, Project project) {
try {
LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class);
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 adf85fd2..9673a9a4 100644
--- a/src/main/java/net/fabricmc/loom/configuration/processors/MinecraftProcessedProvider.java
+++ b/src/main/java/net/fabricmc/loom/configuration/processors/MinecraftProcessedProvider.java
@@ -50,7 +50,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");
invalidateJars();
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 f4511160..a602612e 100644
--- a/src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java
@@ -45,6 +45,7 @@ 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;
public class LaunchProvider extends DependencyProvider {
@@ -69,6 +70,57 @@ public class LaunchProvider extends DependencyProvider {
.argument("client", "--assetsDir")
.argument("client", new File(getExtension().getUserCache(), "assets").getAbsolutePath());
+ if (getExtension().isForge()) {
+ launchConfig
+ .property("fabric.yarnWithSrg.path", getExtension().getMappingsProvider().tinyMappingsWithSrg.toAbsolutePath().toString())
+
+ .argument("--fml.mcVersion")
+ .argument(getExtension().getMinecraftProvider().getMinecraftVersion())
+ .argument("--fml.forgeVersion")
+ .argument(getExtension().getForgeProvider().getVersion().getForgeVersion())
+
+ .argument("client", "--launchTarget")
+ .argument("client", "fmluserdevclient")
+
+ .argument("server", "--launchTarget")
+ .argument("server", "fmluserdevserver")
+
+ .argument("data", "--launchTarget")
+ .argument("data", "fmluserdevdata")
+ .argument("data", "--all")
+ .argument("data", "--mod")
+ .argument("data", String.join(",", getExtension().getDataGenMods()))
+ .argument("data", "--output")
+ .argument("data", getProject().file("src/generated/resources").getAbsolutePath())
+
+ .property("mixin.env.remapRefMap", "true");
+
+ if (getExtension().useFabricMixin) {
+ launchConfig.property("mixin.forgeloom.inject.mappings.srg-named", getExtension().getMappingsProvider().mixinTinyMappingsWithSrg.getAbsolutePath());
+ } else {
+ launchConfig.property("net.minecraftforge.gradle.GradleStart.srg.srg-mcp", getExtension().getMappingsProvider().srgToNamedSrg.getAbsolutePath());
+ }
+
+ List<String> mixinConfigs = getExtension().mixinConfigs;
+
+ if (mixinConfigs != null) {
+ for (String config : mixinConfigs) {
+ launchConfig.argument("-mixin.config");
+ launchConfig.argument(config);
+ }
+ }
+ }
+
+ for (LaunchProviderSettings settings : getExtension().getLaunchConfigs()) {
+ 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());
+ }
+ }
+
//Enable ansi by default for idea and vscode
if (new File(getProject().getRootDir(), ".vscode").exists()
|| new File(getProject().getRootDir(), ".idea").exists()
@@ -83,6 +135,10 @@ public class LaunchProvider extends DependencyProvider {
addDependency(Constants.Dependencies.TERMINAL_CONSOLE_APPENDER + Constants.Dependencies.Versions.TERMINAL_CONSOLE_APPENDER, Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES);
annotationDependency = addDependency(Constants.Dependencies.JETBRAINS_ANNOTATIONS + Constants.Dependencies.Versions.JETBRAINS_ANNOTATIONS, JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME);
+ if (getExtension().isForge()) {
+ addDependency(Constants.Dependencies.JAVAX_ANNOTATIONS + Constants.Dependencies.Versions.JAVAX_ANNOTATIONS, "compileOnly");
+ }
+
postPopulationScheduler.accept(this::writeRemapClassPath);
}
diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/MinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/MinecraftProvider.java
index 508e8c3e..226f3f17 100644
--- a/src/main/java/net/fabricmc/loom/configuration/providers/MinecraftProvider.java
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/MinecraftProvider.java
@@ -34,6 +34,7 @@ import java.util.function.Consumer;
import java.util.zip.ZipError;
import com.google.common.io.Files;
+import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
@@ -56,10 +57,13 @@ public class MinecraftProvider extends DependencyProvider {
private MinecraftLibraryProvider libraryProvider;
private File minecraftJson;
- private File minecraftClientJar;
- private File minecraftServerJar;
+ public File minecraftClientJar;
+ public File minecraftServerJar;
private File minecraftMergedJar;
private File versionManifestJson;
+ private String jarSuffix = "";
+
+ Gson gson = new Gson();
public MinecraftProvider(Project project) {
super(project);
@@ -69,6 +73,10 @@ public class MinecraftProvider extends DependencyProvider {
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();
@@ -119,6 +127,12 @@ public class MinecraftProvider extends DependencyProvider {
versionManifestJson = new File(getExtension().getUserCache(), "version_manifest.json");
}
+ public void deleteFiles() {
+ DownloadUtil.delete(minecraftClientJar);
+ DownloadUtil.delete(minecraftServerJar);
+ DownloadUtil.delete(minecraftMergedJar);
+ }
+
private void downloadMcJson(boolean offline) throws IOException {
if (getExtension().isShareCaches() && !getExtension().isRootProject() && versionManifestJson.exists() && !isRefreshDeps()) {
return;
@@ -241,6 +255,14 @@ public class MinecraftProvider extends DependencyProvider {
return libraryProvider;
}
+ public String getJarSuffix() {
+ return jarSuffix;
+ }
+
+ public void setJarSuffix(String jarSuffix) {
+ this.jarSuffix = jarSuffix;
+ }
+
@Override
public String getTargetConfig() {
return Constants.Configurations.MINECRAFT;
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..d1e0d462
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeProvider.java
@@ -0,0 +1,87 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2016, 2017, 2018 FabricMC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package net.fabricmc.loom.configuration.providers.forge;
+
+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);
+
+ public ForgeProvider(Project project) {
+ super(project);
+ }
+
+ @Override
+ public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception {
+ version = new ForgeVersion(dependency.getDependency().getVersion());
+ addDependency(dependency.getDepString() + ":userdev", Constants.Configurations.FORGE_USERDEV);
+ addDependency(dependency.getDepString() + ":installer", Constants.Configurations.FORGE_INSTALLER);
+ }
+
+ public ForgeVersion getVersion() {
+ return version;
+ }
+
+ @Override
+ public String getTargetConfig() {
+ return Constants.Configurations.FORGE;
+ }
+
+ public static final class ForgeVersion {
+ private final String minecraftVersion;
+ private final String forgeVersion;
+
+ public ForgeVersion(String 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 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..f0c9ba0c
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUniversalProvider.java
@@ -0,0 +1,72 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2016, 2017, 2018 FabricMC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package net.fabricmc.loom.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;
+import net.fabricmc.loom.util.JarUtil;
+
+public class ForgeUniversalProvider extends DependencyProvider {
+ private File forge;
+ private File forgeManifest;
+
+ public ForgeUniversalProvider(Project project) {
+ super(project);
+ }
+
+ @Override
+ public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception {
+ forge = new File(getExtension().getProjectPersistentCache(), "forge-" + dependency.getDependency().getVersion() + "-universal.jar");
+ forgeManifest = new File(getExtension().getProjectPersistentCache(), "forge-" + dependency.getDependency().getVersion() + "-manifest.mf");
+
+ if (!forge.exists() || isRefreshDeps()) {
+ File dep = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve Forge"));
+ FileUtils.copyFile(dep, forge);
+ }
+
+ if (!forgeManifest.exists() || isRefreshDeps()) {
+ JarUtil.extractFile(forge, "META-INF/MANIFEST.MF", forgeManifest);
+ }
+ }
+
+ public File getForge() {
+ return forge;
+ }
+
+ public File getForgeManifest() {
+ return forgeManifest;
+ }
+
+ @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..de0394ce
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java
@@ -0,0 +1,105 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2016, 2017, 2018 FabricMC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package net.fabricmc.loom.configuration.providers.forge;
+
+import java.io.File;
+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.util.function.Consumer;
+
+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 net.fabricmc.loom.configuration.DependencyProvider;
+import net.fabricmc.loom.util.Constants;
+
+public class ForgeUserdevProvider extends DependencyProvider {
+ private File userdevJar;
+
+ public ForgeUserdevProvider(Project project) {
+ super(project);
+ }
+
+ @Override
+ public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception {
+ userdevJar = new File(getExtension().getProjectPersistentCache(), "forge-" + dependency.getDependency().getVersion() + "-userdev.jar");
+
+ Path configJson = getExtension()
+ .getProjectPersistentCache()
+ .toPath()
+ .resolve("forge-config-" + dependency.getDependency().getVersion() + ".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);
+ }
+ }
+
+ JsonObject json;
+
+ 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()) {
+ if (lib.getAsString().startsWith("org.spongepowered:mixin:")) {
+ if (getExtension().useFabricMixin) {
+ addDependency("net.fabricmc:sponge-mixin:0.8.2+build.24", Constants.Configurations.FORGE_DEPENDENCIES);
+ continue;
+ }
+ }
+
+ addDependency(lib.getAsString(), Constants.Configurations.FORGE_DEPENDENCIES);
+ }
+
+ // TODO: Read launch configs from the JSON too
+ // TODO: Should I copy the patches from here as well?
+ // That'd require me to run the "MCP environment" fully up to merging.
+ }
+
+ 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..037f4710
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java
@@ -0,0 +1,72 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2016, 2017, 2018 FabricMC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package net.fabricmc.loom.configuration.providers.forge;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.function.Consumer;
+
+import org.gradle.api.Project;
+
+import net.fabricmc.loom.configuration.DependencyProvider;
+import net.fabricmc.loom.util.Constants;
+
+public class McpConfigProvider extends DependencyProvider {
+ private File mcp;
+
+ public McpConfigProvider(Project project) {
+ super(project);
+ }
+
+ @Override
+ public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception {
+ init(dependency.getDependency().getVersion());
+
+ if (mcp.exists() && !isRefreshDeps()) {
+ return; // No work for us to do here
+ }
+
+ Path mcpZip = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve MCPConfig")).toPath();
+
+ if (!mcp.exists() || isRefreshDeps()) {
+ Files.copy(mcpZip, mcp.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ }
+ }
+
+ private void init(String version) {
+ mcp = new File(getExtension().getUserCache(), "mcp-" + version + ".zip");
+ }
+
+ public File getMcp() {
+ return mcp;
+ }
+
+ @Override
+ public String getTargetConfig() {
+ return Constants.Configurations.MCP_CONFIG;
+ }
+}
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..cccfc7f8
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java
@@ -0,0 +1,585 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2016, 2017, 2018 FabricMC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package net.fabricmc.loom.configuration.providers.forge;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.io.UncheckedIOException;
+import java.lang.reflect.Field;
+import java.net.URI;
+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.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Locale;
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableMap;
+import com.google.gson.JsonParser;
+import de.oceanlabs.mcp.mcinjector.adaptors.ParameterAnnotationFixer;
+import net.minecraftforge.accesstransformer.AccessTransformerEngine;
+import net.minecraftforge.accesstransformer.TransformerProcessor;
+import net.minecraftforge.accesstransformer.parser.AccessTransformerList;
+import net.minecraftforge.binarypatcher.ConsoleTool;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.output.NullOutputStream;
+import org.gradle.api.Project;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.plugins.JavaPluginConvention;
+import org.gradle.api.tasks.SourceSet;
+import org.jetbrains.annotations.Nullable;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.tree.ClassNode;
+import org.zeroturnaround.zip.ZipUtil;
+
+import net.fabricmc.loom.configuration.DependencyProvider;
+import net.fabricmc.loom.configuration.providers.MinecraftProvider;
+import net.fabricmc.loom.configuration.providers.mappings.MappingsProvider;
+import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider;
+import net.fabricmc.loom.util.Checksum;
+import net.fabricmc.loom.util.Constants;
+import net.fabricmc.loom.util.DownloadUtil;
+import net.fabricmc.loom.util.FileSystemUtil;
+import net.fabricmc.loom.util.JarUtil;
+import net.fabricmc.loom.util.ThreadingUtils;
+import net.fabricmc.loom.util.TinyRemapperMappingsHelper;
+import net.fabricmc.loom.util.function.FsPathConsumer;
+import net.fabricmc.loom.util.srg.InnerClassRemapper;
+import net.fabricmc.loom.util.srg.SpecialSourceExecutor;
+import net.fabricmc.mapping.tree.TinyTree;
+import net.fabricmc.tinyremapper.OutputConsumerPath;
+import net.fabricmc.tinyremapper.TinyRemapper;
+
+public class MinecraftPatchedProvider extends DependencyProvider {
+ private final MappingsProvider mappingsProvider;
+ // Step 1: Remap Minecraft to SRG
+ private File minecraftClientSrgJar;
+ private File minecraftServerSrgJar;
+ // Step 2: Binary Patch
+ private File minecraftClientPatchedSrgJar;
+ private File minecraftServerPatchedSrgJar;
+ // Step 3: Access Transform
+ private File minecraftClientPatchedSrgATJar;
+ private File minecraftServerPatchedSrgATJar;
+ // Step 4: Remap Patched AT to Official
+ private File minecraftClientPatchedOfficialJar;
+ private File minecraftServerPatchedOfficialJar;
+ // Step 5: Merge
+ private File minecraftMergedPatchedJar;
+ private File projectAtHash;
+ @Nullable
+ private File projectAt = null;
+ private boolean atDirty = false;
+
+ public MinecraftPatchedProvider(MappingsProvider mappingsProvider, Project project) {
+ super(project);
+ this.mappingsProvider = mappingsProvider;
+ }
+
+ public void initFiles() throws IOException {
+ projectAtHash = new File(getExtension().getProjectPersistentCache(), "at.sha256");
+
+ SourceSet main = getProject().getConvention().findPlugin(JavaPluginConvention.class).getSourceSets().getByName("main");
+
+ for (File srcDir : main.getResources().getSrcDirs()) {
+ File projectAt = new File(srcDir, "META-INF/accesstransformer.cfg");
+
+ if (projectAt.exists()) {
+ this.projectAt = projectAt;
+ break;
+ }
+ }
+
+ if (isRefreshDeps() || !projectAtHash.exists()) {
+ writeAtHash();
+ atDirty = projectAt != null;
+ } else {
+ byte[] expected = com.google.common.io.Files.asByteSource(projectAtHash).read();
+ byte[] current = projectAt != null ? Checksum.sha256(projectAt) : Checksum.sha256("");
+ boolean mismatched = !Arrays.equals(current, expected);
+
+ if (mismatched) {
+ writeAtHash();
+ }
+
+ atDirty = mismatched;
+ }
+
+ MinecraftProvider minecraftProvider = getExtension().getMinecraftProvider();
+ PatchProvider patchProvider = getExtension().getPatchProvider();
+ String minecraftVersion = minecraftProvider.getMinecraftVersion();
+ String jarSuffix = "-patched-forge-" + patchProvider.forgeVersion;
+
+ if (getExtension().useFabricMixin) {
+ jarSuffix += "-fabric-mixin";
+ }
+
+ minecraftProvider.setJarSuffix(jarSuffix);
+
+ File globalCache = getExtension().getUserCache();
+ File cache = usesProjectCache() ? getExtension().getProjectPersistentCache() : globalCache;
+
+ minecraftClientSrgJar = new File(globalCache, "minecraft-" + minecraftVersion + "-client-srg.jar");
+ minecraftServerSrgJar = new File(globalCache, "minecraft-" + minecraftVersion + "-server-srg.jar");
+ minecraftClientPatchedSrgJar = new File(globalCache, "minecraft-" + minecraftVersion + "-client-srg" + jarSuffix + ".jar");
+ minecraftServerPatchedSrgJar = new File(globalCache, "minecraft-" + minecraftVersion + "-server-srg" + jarSuffix + ".jar");
+ minecraftClientPatchedSrgATJar = new File(cache, "minecraft-" + minecraftVersion + "-client-srg-at" + jarSuffix + ".jar");
+ minecraftServerPatchedSrgATJar = new File(cache, "minecraft-" + minecraftVersion + "-server-srg-at" + jarSuffix + ".jar");
+ minecraftClientPatchedOfficialJar = new File(cache, "minecraft-" + minecraftVersion + "-client" + jarSuffix + ".jar");
+ minecraftServerPatchedOfficialJar = new File(cache, "minecraft-" + minecraftVersion + "-server" + jarSuffix + ".jar");
+ minecraftMergedPatchedJar = new File(cache, "minecraft-" + minecraftVersion + "-merged" + jarSuffix + ".jar");
+
+ if (isRefreshDeps() || Stream.of(getGlobalCaches()).anyMatch(Predicates.not(File::exists))) {
+ cleanAllCache();
+ } else if (atDirty || Stream.of(getProjectCache()).anyMatch(Predicates.not(File::exists))) {
+ cleanProjectCache();
+ }
+ }
+
+ public void cleanAllCache() {
+ for (File file : getGlobalCaches()) {
+ file.delete();
+ }
+
+ cleanProjectCache();
+ }
+
+ private File[] getGlobalCaches() {
+ return new File[] {
+ minecraftClientSrgJar,
+ minecraftServerSrgJar,
+ minecraftClientPatchedSrgJar,
+ minecraftServerPatchedSrgJar
+ };
+ }
+
+ public void cleanProjectCache() {
+ for (File file : getProjectCache()) {
+ file.delete();
+ }
+ }
+
+ private File[] getProjectCache() {
+ return new File[] {
+ minecraftClientPatchedSrgATJar,
+ minecraftServerPatchedSrgATJar,
+ minecraftClientPatchedOfficialJar,
+ minecraftServerPatchedOfficialJar,
+ minecraftMergedPatchedJar
+ };
+ }
+
+ @Override
+ public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception {
+ initFiles();
+
+ if (atDirty) {
+ getProject().getLogger().lifecycle(":found dirty access transformers");
+ }
+
+ boolean dirty = false;
+
+ if (!minecraftClientSrgJar.exists() || !minecraftServerSrgJar.exists()) {
+ dirty = true;
+ // Remap official jars to MCPConfig remapped srg jars
+ createSrgJars(getProject().getLogger());
+ }
+
+ if (!minecraftClientPatchedSrgJar.exists() || !minecraftServerPatchedSrgJar.exists()) {
+ dirty = true;
+ patchJars(getProject().getLogger());
+ injectForgeClasses(getProject().getLogger());
+ }
+
+ if (atDirty || !minecraftClientPatchedSrgATJar.exists() || !minecraftServerPatchedSrgATJar.exists()) {
+ dirty = true;
+ accessTransformForge(getProject().getLogger());
+ }
+
+ if (dirty) {
+ remapPatchedJars(getProject().getLogger());
+ }
+
+ if (dirty || !minecraftMergedPatchedJar.exists()) {
+ mergeJars(getProject().getLogger());
+ }
+ }
+
+ private void writeAtHash() throws IOException {
+ try (FileOutputStream out = new FileOutputStream(projectAtHash)) {
+ if (projectAt != null) {
+ out.write(Checksum.sha256(projectAt));
+ } else {
+ out.write(Checksum.sha256(""));
+ }
+ }
+ }
+
+ private void createSrgJars(Logger logger) throws Exception {
+ McpConfigProvider mcpProvider = getExtension().getMcpConfigProvider();
+
+ MinecraftProvider minecraftProvider = getExtension().getMinecraftProvider();
+
+ String[] mappingsPath = {null};
+
+ if (!ZipUtil.handle(mcpProvider.getMcp(), "config.json", (in, zipEntry) -> {
+ mappingsPath[0] = new JsonParser().parse(new InputStreamReader(in)).getAsJsonObject().get("data").getAsJsonObject().get("mappings").getAsString();
+ })) {
+ throw new IllegalStateException("Failed to find 'config.json' in " + mcpProvider.getMcp().getAbsolutePath() + "!");
+ }
+
+ Path[] tmpSrg = {null};
+
+ if (!ZipUtil.handle(mcpProvider.getMcp(), mappingsPath[0], (in, zipEntry) -> {
+ tmpSrg[0] = Files.createTempFile(null, null);
+
+ try (BufferedWriter writer = Files.newBufferedWriter(tmpSrg[0])) {
+ IOUtils.copy(in, writer, StandardCharsets.UTF_8);
+ }
+ })) {
+ throw new IllegalStateException("Failed to find mappings '" + mappingsPath[0] + "' in " + mcpProvider.getMcp().getAbsolutePath() + "!");
+ }
+
+ File specialSourceJar = new File(getExtension().getUserCache(), "SpecialSource-1.8.3-shaded.jar");
+ DownloadUtil.downloadIfChanged(new URL("https://repo1.maven.org/maven2/net/md-5/SpecialSource/1.8.3/SpecialSource-1.8.3-shaded.jar"), specialSourceJar, getProject().getLogger(), true);
+
+ ThreadingUtils.run(() -> {
+ Files.copy(SpecialSourceExecutor.produceSrgJar(getProject(), mappingsProvider, "client", specialSourceJar, minecraftProvider.minecraftClientJar.toPath(), tmpSrg[0]), minecraftClientSrgJar.toPath());
+ }, () -> {
+ Files.copy(SpecialSourceExecutor.produceSrgJar(getProject(), mappingsProvider, "server", specialSourceJar, minecraftProvider.minecraftServerJar.toPath(), tmpSrg[0]), minecraftServerSrgJar.toPath());
+ });
+ }
+
+ private void fixParameterAnnotation(File jarFile) throws Exception {
+ getProject().getLogger().info(":fixing parameter annotations for " + jarFile.toString());
+
+ try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + jarFile.toURI()), ImmutableMap.of("create", false))) {
+ for (Path rootDir : fs.getRootDirectories()) {
+ for (Path file : (Iterable<? extends Path>) Files.walk(rootDir)::iterator) {
+ if (!file.toString().endsWith(".class")) continue;
+ 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);
+ }
+ }
+ }
+ }
+ }
+
+ private void injectForgeClasses(Logger logger) throws IOException {
+ logger.lifecycle(":injecting forge classes into minecraft");
+ ThreadingUtils.run(Arrays.asList(Environment.values()), environment -> {
+ copyAll(getExtension().getForgeUniversalProvider().getForge(), environment.patchedSrgJar.apply(this));
+ copyUserdevFiles(getExtension().getForgeUserdevProvider().getUserdevJar(), environment.patchedSrgJar.apply(this));
+ });
+
+ logger.lifecycle(":injecting loom classes into minecraft");
+ File injection = File.createTempFile("loom-injection", ".jar");
+
+ try (InputStream in = MinecraftProvider.class.getResourceAsStream("/inject/injection.jar")) {
+ FileUtils.copyInputStreamToFile(in, injection);
+ }
+
+ for (Environment environment : Environment.values()) {
+ String side = environment.side();
+ File target = environment.patchedSrgJar.apply(this);
+ walkFileSystems(injection, target, it -> {
+ if (it.getFileName().toString().equals("MANIFEST.MF")) {
+ return false;
+ }
+
+ return getExtension().useFabricMixin || !it.getFileName().toString().endsWith("cpw.mods.modlauncher.api.ITransformationService");
+ }, this::copyReplacing);
+ }
+ }
+
+ private void accessTransformForge(Logger logger) throws Exception {
+ for (Environment environment : Environment.values()) {
+ String side = environment.side();
+ logger.lifecycle(":access transforming minecraft (" + side + ")");
+
+ File input = environment.patchedSrgJar.apply(this);
+ File inputCopied = File.createTempFile("at" + side, ".jar");
+ FileUtils.copyFile(input, inputCopied);
+ File target = environment.patchedSrgATJar.apply(this);
+ target.delete();
+ File at = File.createTempFile("at" + side, ".cfg");
+ JarUtil.extractFile(inputCopied, "META-INF/accesstransformer.cfg", at);
+ String[] args = new String[] {
+ "--inJar", inputCopied.getAbsolutePath(),
+ "--outJar", target.getAbsolutePath(),
+ "--atFile", at.getAbsolutePath()
+ };
+
+ if (usesProjectCache()) {
+ args = Arrays.copyOf(args, args.length + 2);
+ args[args.length - 2] = "--atFile";
+ args[args.length - 1] = projectAt.getAbsolutePath();
+ }
+
+ resetAccessTransformerEngine();
+ TransformerProcessor.main(args);
+ inputCopied.delete();
+ }
+ }
+
+ private void resetAccessTransformerEngine() throws Exception {
+ // Thank you Forge, I love you
+ Field field = AccessTransformerEngine.class.getDeclaredField("masterList");
+ field.setAccessible(true);
+ AccessTransformerList list = (AccessTransformerList) field.get(AccessTransformerEngine.INSTANCE);
+ field = AccessTransformerList.class.getDeclaredField("accessTransformers");
+ field.setAccessible(true);
+ ((Map<?, ?>) field.get(list)).clear();
+ }
+
+ private enum Environment {
+ CLIENT(provider -> provider.minecraftClientSrgJar,
+ provider -> provider.minecraftClientPatchedSrgJar,
+ provider -> provider.minecraftClientPatchedSrgATJar,
+ provider -> provider.minecraftClientPatchedOfficialJar
+ ),
+ SERVER(provider -> provider.minecraftServerSrgJar,
+ provider -> provider.minecraftServerPatchedSrgJar,
+ provider -> provider.minecraftServerPatchedSrgATJar,
+ provider -> provider.minecraftServerPatchedOfficialJar
+ );
+
+ final Function<MinecraftPatchedProvider, File> srgJar;
+ final Function<MinecraftPatchedProvider, File> patchedSrgJar;
+ final Function<MinecraftPatchedProvider, File> patchedSrgATJar;
+ final Function<MinecraftPatchedProvider, File> patchedOfficialJar;
+
+ Environment(Function<MinecraftPatchedProvider, File> srgJar,
+ Function<MinecraftPatchedProvider, File> patchedSrgJar,
+ Function<MinecraftPatchedProvider, File> patchedSrgATJar,
+ Function<MinecraftPatchedProvider, File> patchedOfficialJar) {
+ this.srgJar = srgJar;
+ this.patchedSrgJar = patchedSrgJar;
+ this.patchedSrgATJar = patchedSrgATJar;
+ this.patchedOfficialJar = patchedOfficialJar;
+ }
+
+ public String side() {
+ return name().toLowerCase(Locale.ROOT);
+ }
+ }
+
+ private void remapPatchedJars(Logger logger) throws Exception {
+ Path[] libraries = MinecraftMappedProvider.getRemapClasspath(getProject());
+
+ ThreadingUtils.run(Arrays.asList(Environment.values()), environment -> {
+ logger.lifecycle(":remapping minecraft (TinyRemapper, " + environment.side() + ", srg -> official)");
+ TinyTree mappingsWithSrg = getExtension().getMappingsProvider().getMappingsWithSrg();
+
+ Path input = environment.patchedSrgATJar.apply(this).toPath();
+ Path output = environment.patchedOfficialJar.apply(this).toPath();
+
+ Files.deleteIfExists(output);
+
+ TinyRemapper remapper = TinyRemapper.newRemapper()
+ .logger(getProject().getLogger()::lifecycle)
+ .withMappings(TinyRemapperMappingsHelper.create(mappingsWithSrg, "srg", "official", true))
+ .withMappings(InnerClassRemapper.of(input, mappingsWithSrg, "srg", "official"))
+ .renameInvalidLocals(true)
+ .rebuildSourceFilenames(true)
+ .fixPackageAccess(true)
+ .build();
+
+ try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(output).build()) {
+ outputConsumer.addNonClassFiles(input);
+
+ remapper.readClassPath(libraries);
+ remapper.readInputs(input);
+ remapper.apply(outputConsumer);
+ } finally {
+ remapper.finish();
+ }
+ });
+ }
+
+ private void patchJars(Logger logger) throws IOException {
+ logger.lifecycle(":patching jars");
+
+ PatchProvider patchProvider = getExtension().getPatchProvider();
+ patchJars(minecraftClientSrgJar, minecraftClientPatchedSrgJar, patchProvider.clientPatches);
+ patchJars(minecraftServerSrgJar, minecraftServerPatchedSrgJar, patchProvider.serverPatches);
+
+ ThreadingUtils.run(Arrays.asList(Environment.values()), environment -> {
+ copyMissingClasses(environment.srgJar.apply(this), environment.patchedSrgJar.apply(this));
+ fixParameterAnnotation(environment.patchedSrgJar.apply(this));
+ });
+ }
+
+ private void patchJars(File clean, File output, Path patches) throws IOException {
+ PrintStream previous = System.out;
+
+ try {
+ System.setOut(new PrintStream(new NullOutputStream()));
+ } 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.
+ FileUtils.copyFile(minecraftClientPatchedOfficialJar, minecraftMergedPatchedJar);
+
+ logger.lifecycle(":copying resources");
+
+ // Copy resources
+ MinecraftProvider minecraftProvider = getExtension().getMinecraftProvider();
+ copyNonClassFiles(minecraftProvider.minecraftClientJar, minecraftMergedPatchedJar);
+ copyNonClassFiles(minecraftProvider.minecraftServerJar, minecraftMergedPatchedJar);
+ }
+
+ private void walkFileSystems(File source, File target, Predicate<Path> filter, Function<FileSystem, Iterable<Path>> toWalk, FsPathConsumer action)
+ throws IOException {
+ try (FileSystemUtil.FileSystemDelegate sourceFs = FileSystemUtil.getJarFileSystem(source, false);
+ FileSystemUtil.FileSystemDelegate targetFs = FileSystemUtil.getJarFileSystem(target, false)) {
+ for (Path sourceDir : toWalk.apply(sourceFs.get())) {
+ Path dir = sourceDir.toAbsolutePath();
+ 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 {
+ walkFileSystems(source, target, it -> !it.toString().endsWith(".class"), 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 {
+ walkFileSystems(source, target, file -> true, 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 File getMergedJar() {
+ return minecraftMergedPatchedJar;
+ }
+
+ public boolean usesProjectCache() {
+ return projectAt != null;
+ }
+
+ public boolean isAtDirty() {
+ return atDirty;
+ }
+
+ @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..24f1c866
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java
@@ -0,0 +1,76 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2016, 2017, 2018 FabricMC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package net.fabricmc.loom.configuration.providers.forge;
+
+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 String forgeVersion;
+
+ 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) {
+ this.forgeVersion = forgeVersion;
+ clientPatches = getExtension().getProjectPersistentCache().toPath().resolve("patches-" + forgeVersion + "-client.lzma");
+ serverPatches = getExtension().getProjectPersistentCache().toPath().resolve("patches-" + forgeVersion + "-server.lzma");
+ }
+
+ @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..1fe73c21
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java
@@ -0,0 +1,78 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2016, 2017, 2018 FabricMC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package net.fabricmc.loom.configuration.providers.forge;
+
+import java.io.File;
+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 SrgProvider extends DependencyProvider {
+ private File srg;
+
+ public SrgProvider(Project project) {
+ super(project);
+ }
+
+ @Override
+ public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception {
+ init(dependency.getDependency().getVersion());
+
+ if (srg.exists() && !isRefreshDeps()) {
+ return; // No work for us to do here
+ }
+
+ Path srgZip = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve srg")).toPath();
+
+ if (!srg.exists() || isRefreshDeps()) {
+ try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + srgZip.toUri()), ImmutableMap.of("create", false))) {
+ Files.copy(fs.getPath("config", "joined.tsrg"), srg.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ }
+ }
+ }
+
+ private void init(String version) {
+ srg = new File(getExtension().getUserCache(), "srg-" + version + ".tsrg");
+ }
+
+ public File getSrg() {
+ return srg;
+ }
+
+ @Override
+ public String getTargetConfig() {
+ return Constants.Configurations.SRG;
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProvider.java
index 448082b9..40ae05d0 100644
--- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProvider.java
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProvider.java
@@ -32,9 +32,12 @@ 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.Paths;
import java.nio.file.StandardCopyOption;
+import java.util.ArrayList;
+import java.util.List;
import java.util.function.Consumer;
import com.google.common.base.Preconditions;
@@ -43,6 +46,7 @@ import com.google.gson.JsonObject;
import org.apache.commons.io.FileUtils;
import org.apache.tools.ant.util.StringUtils;
import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
import org.zeroturnaround.zip.FileSource;
import org.zeroturnaround.zip.ZipEntrySource;
import org.zeroturnaround.zip.ZipUtil;
@@ -54,19 +58,27 @@ import net.fabricmc.loom.configuration.accesswidener.AccessWidenerJarProcessor;
import net.fabricmc.loom.configuration.processors.JarProcessorManager;
import net.fabricmc.loom.configuration.processors.MinecraftProcessedProvider;
import net.fabricmc.loom.configuration.providers.MinecraftProvider;
+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.srg.MCPReader;
+import net.fabricmc.loom.util.srg.SrgMerger;
+import net.fabricmc.loom.util.srg.SrgNamedWriter;
import net.fabricmc.mapping.reader.v2.TinyV2Factory;
import net.fabricmc.mapping.tree.TinyTree;
import net.fabricmc.stitch.Command;
import net.fabricmc.stitch.commands.CommandProposeFieldNames;
import net.fabricmc.stitch.commands.tinyv2.CommandMergeTinyV2;
import net.fabricmc.stitch.commands.tinyv2.CommandReorderTinyV2;
+import net.fabricmc.stitch.commands.tinyv2.TinyFile;
+import net.fabricmc.stitch.commands.tinyv2.TinyV2Writer;
public class MappingsProvider extends DependencyProvider {
public MinecraftMappedProvider mappedProvider;
+ public MinecraftPatchedProvider patchedProvider;
public String mappingsName;
public String minecraftVersion;
@@ -81,6 +93,11 @@ public class MappingsProvider extends DependencyProvider {
// The mappings we use in practice
public File tinyMappings;
public File tinyMappingsJar;
+ public File mappingsMixinExport;
+ public Path tinyMappingsWithSrg;
+ public File mixinTinyMappingsWithSrg; // FORGE: The mixin mappings have srg names in intermediary.
+ public File srgToNamedSrg; // FORGE: srg to named in srg file format
+
private File unpickDefinitionsFile;
private boolean hasUnpickDefinitions;
private UnpickMetadata unpickMetadata;
@@ -99,6 +116,14 @@ public class MappingsProvider extends DependencyProvider {
return MappingsCache.INSTANCE.get(tinyMappings.toPath());
}
+ public TinyTree getMappingsWithSrg() throws IOException {
+ if (getExtension().shouldGenerateSrgTiny()) {
+ return MappingsCache.INSTANCE.get(tinyMappingsWithSrg);
+ }
+
+ throw new UnsupportedOperationException("Not running with Forge support / Tiny srg support.");
+ }
+
@Override
public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception {
MinecraftProvider minecraftProvider = getDependencyManager().getProvider(MinecraftProvider.class);
@@ -113,6 +138,13 @@ public class MappingsProvider extends DependencyProvider {
boolean isV2;
+ if (isMCP(mappingsJar.toPath())) {
+ File old = mappingsJar;
+ mappingsJar = mappingsDir.resolve(StringUtils.removeSuffix(mappingsJar.getName(), ".zip") + "-" + minecraftVersion + ".jar").toFile();
+ FileUtils.copyFile(old, mappingsJar);
+ mappingsName += "-" + minecraftVersion;
+ }
+
// Only do this for official yarn, there isn't really a way we can get the mc version for all mappings
if (dependency.getDependency().getGroup() != null && dependency.getDependency().getGroup().equals("net.fabricmc") && dependency.getDependency().getName().equals("yarn") && dependency.getDependency().getVersion() != null) {
String yarnVersion = dependency.getDependency().getVersion();
@@ -150,9 +182,12 @@ public class MappingsProvider extends DependencyProvider {
tinyMappings = mappingsDir.resolve(StringUtils.removeSuffix(mappingsJar.getName(), ".jar") + ".tiny").toFile();
unpickDefinitionsFile = mappingsDir.resolve(StringUtils.removeSuffix(mappingsJar.getName(), ".jar") + ".unpick").toFile();
tinyMappingsJar = new File(getExtension().getUserCache(), mappingsJar.getName().replace(".jar", "-" + jarClassifier + ".jar"));
+ tinyMappingsWithSrg = mappingsDir.resolve(StringUtils.removeSuffix(mappingsJar.getName(), ".jar") + "-srg.tiny");
+ mixinTinyMappingsWithSrg = mappingsDir.resolve(StringUtils.removeSuffix(mappingsJar.getName(), ".jar") + "-mixin-srg.tiny").toFile();
+ srgToNamedSrg = mappingsDir.resolve(StringUtils.removeSuffix(mappingsJar.getName(), ".jar") + "-srg-named.srg").toFile();
if (!tinyMappings.exists() || isRefreshDeps()) {
- storeMappings(getProject(), minecraftProvider, mappingsJar.toPath());
+ storeMappings(getProject(), minecraftProvider, mappingsJar.toPath(), postPopulationScheduler);
} else {
try (FileSystem fileSystem = FileSystems.newFileSystem(mappingsJar.toPath(), (ClassLoader) null)) {
extractUnpickDefinitions(fileSystem, unpickDefinitionsFile.toPath());
@@ -174,6 +209,29 @@ public class MappingsProvider extends DependencyProvider {
populateUnpickClasspath();
}
+ if (getExtension().shouldGenerateSrgTiny()) {
+ if (Files.notExists(tinyMappingsWithSrg) || isRefreshDeps()) {
+ SrgMerger.mergeSrg(getExtension().getSrgProvider().getSrg().toPath(), tinyMappings.toPath(), tinyMappingsWithSrg, true);
+ }
+ }
+
+ if (getExtension().isForge()) {
+ if (!getExtension().shouldGenerateSrgTiny()) {
+ throw new IllegalStateException("We have to generate srg tiny in a forge environment!");
+ }
+
+ if (!mixinTinyMappingsWithSrg.exists() || isRefreshDeps()) {
+ List<String> lines = new ArrayList<>(Files.readAllLines(tinyMappingsWithSrg));
+ lines.set(0, lines.get(0).replace("intermediary", "yraidemretni").replace("srg", "intermediary"));
+ Files.deleteIfExists(mixinTinyMappingsWithSrg.toPath());
+ Files.write(mixinTinyMappingsWithSrg.toPath(), lines);
+ }
+
+ if (!srgToNamedSrg.exists() || isRefreshDeps()) {
+ SrgNamedWriter.writeTo(getProject().getLogger(), srgToNamedSrg.toPath(), getMappingsWithSrg(), "srg", "named");
+ }
+ }
+
addDependency(tinyMappingsJar, Constants.Configurations.MAPPINGS_FINAL);
LoomGradleExtension extension = getExtension();
@@ -186,7 +244,12 @@ public class MappingsProvider extends DependencyProvider {
extension.setJarProcessorManager(processorManager);
processorManager.setupProcessors();
- if (processorManager.active()) {
+ if (extension.isForge()) {
+ patchedProvider = new MinecraftPatchedProvider(this, getProject());
+ patchedProvider.provide(dependency, postPopulationScheduler);
+ }
+
+ if (processorManager.active() || (extension.isForge() && patchedProvider.usesProjectCache())) {
mappedProvider = new MinecraftProcessedProvider(getProject(), processorManager);
getProject().getLogger().lifecycle("Using project based jar storage");
} else {
@@ -197,9 +260,15 @@ public class MappingsProvider extends DependencyProvider {
mappedProvider.provide(dependency, postPopulationScheduler);
}
- private void storeMappings(Project project, MinecraftProvider minecraftProvider, Path yarnJar) throws IOException {
+ private void storeMappings(Project project, MinecraftProvider minecraftProvider, Path yarnJar, Consumer<Runnable> postPopulationScheduler)
+ throws Exception {
project.getLogger().info(":extracting " + yarnJar.getFileName());
+ if (isMCP(yarnJar)) {
+ readAndMergeMCP(yarnJar, postPopulationScheduler);
+ return;
+ }
+
try (FileSystem fileSystem = FileSystems.newFileSystem(yarnJar, (ClassLoader) null)) {
extractMappings(fileSystem, baseTinyMappings);
extractUnpickDefinitions(fileSystem, unpickDefinitionsFile.toPath());
@@ -219,6 +288,34 @@ public class MappingsProvider extends DependencyProvider {
}
}
+ 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:" + minecraftVersion);
+ Configuration configuration = getProject().getConfigurations().getByName(provider.getTargetConfig());
+ provider.provide(DependencyInfo.create(getProject(), configuration.getDependencies().iterator().next(), configuration), postPopulationScheduler);
+ }
+
+ Path srgPath = provider.getSrg().toPath();
+
+ TinyFile file = new MCPReader(intermediaryTinyPath, srgPath).read(mcpJar);
+ TinyV2Writer.write(file, tinyMappings.toPath());
+ }
+
+ 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 boolean baseMappingsAreV2() throws IOException {
try (BufferedReader reader = Files.newBufferedReader(baseTinyMappings)) {
TinyV2Factory.readMetadata(reader);
@@ -234,7 +331,7 @@ public class MappingsProvider extends DependencyProvider {
try (BufferedReader reader = Files.newBufferedReader(fs.getPath("mappings", "mappings.tiny"))) {
TinyV2Factory.readMetadata(reader);
return true;
- } catch (IllegalArgumentException e) {
+ } catch (IllegalArgumentException | NoSuchFileException e) {
return false;
}
}
@@ -315,20 +412,20 @@ public class MappingsProvider extends DependencyProvider {
try {
Command command = new CommandMergeTinyV2();
runCommand(command, intermediaryMappings.toAbsolutePath().toString(),
- yarnMappings.toAbsolutePath().toString(),
- newMergedMappings.toAbsolutePath().toString(),
- "intermediary", "official");
+ yarnMappings.toAbsolutePath().toString(),
+ newMergedMappings.toAbsolutePath().toString(),
+ "intermediary", "official");
} catch (Exception e) {
throw new RuntimeException("Could not merge mappings from " + intermediaryMappings.toString()
- + " with mappings from " + yarnMappings, e);
+ + " with mappings from " + yarnMappings, e);
}
}
private void suggestFieldNames(MinecraftProvider 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) {
diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MojangMappingsDependency.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MojangMappingsDependency.java
index 62879458..09010c87 100644
--- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MojangMappingsDependency.java
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MojangMappingsDependency.java
@@ -45,9 +45,19 @@ 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.gradle.api.Action;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Dependency;
+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.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.tasks.TaskDependency;
import org.zeroturnaround.zip.ByteSource;
import org.zeroturnaround.zip.ZipEntrySource;
@@ -60,7 +70,7 @@ import net.fabricmc.loom.util.HashedDownloadUtil;
import net.fabricmc.lorenztiny.TinyMappingsReader;
import net.fabricmc.mapping.tree.TinyMappingFactory;
-public class MojangMappingsDependency implements SelfResolvingDependency {
+public class MojangMappingsDependency extends AbstractModuleDependency implements SelfResolvingDependency, ExternalModuleDependency {
public static final String GROUP = "net.minecraft";
public static final String MODULE = "mappings";
// Keys in dependency manifest
@@ -70,12 +80,51 @@ public class MojangMappingsDependency implements SelfResolvingDependency {
private final Project project;
private final LoomGradleExtension extension;
+ private boolean changing;
+ private boolean force;
+
public MojangMappingsDependency(Project project, LoomGradleExtension extension) {
+ super(null);
this.project = project;
this.extension = extension;
}
@Override
+ public ExternalModuleDependency copy() {
+ MojangMappingsDependency copiedProjectDependency = new MojangMappingsDependency(project, extension);
+ this.copyTo(copiedProjectDependency);
+ return copiedProjectDependency;
+ }
+
+ @Override
+ public void version(Action<? super MutableVersionConstraint> action) {
+ }
+
+ @Override
+ public boolean isForce() {
+ return this.force;
+ }
+
+ @Override
+ public ExternalModuleDependency setForce(boolean force) {
+ this.validateMutation(this.force, force);
+ this.force = force;
+ return this;
+ }
+
+ @Override
+ public boolean isChanging() {
+ return this.changing;
+ }
+
+ @Override
+ public ExternalModuleDependency setChanging(boolean changing) {
+ this.validateMutation(this.changing, changing);
+ this.changing = changing;
+ return this;
+ }
+
+ @Override
public Set<File> resolve() {
Path mappingsDir = extension.getMappingsProvider().getMappingsDir();
Path mappingsFile = mappingsDir.resolve(String.format("%s.%s-%s.tiny", GROUP, MODULE, getVersion()));
@@ -101,20 +150,22 @@ public class MojangMappingsDependency implements SelfResolvingDependency {
}
}
- try (BufferedReader clientBufferedReader = Files.newBufferedReader(clientMappings, StandardCharsets.UTF_8)) {
- project.getLogger().warn("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
- project.getLogger().warn("Using of the official minecraft mappings is at your own risk!");
- project.getLogger().warn("Please make sure to read and understand the following license:");
- project.getLogger().warn("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
- String line;
+ if (!extension.isSilentMojangMappingsLicenseEnabled()) {
+ try (BufferedReader clientBufferedReader = Files.newBufferedReader(clientMappings, StandardCharsets.UTF_8)) {
+ project.getLogger().warn("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
+ project.getLogger().warn("Using of the official minecraft mappings is at your own risk!");
+ project.getLogger().warn("Please make sure to read and understand the following license:");
+ project.getLogger().warn("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
+ String line;
- while ((line = clientBufferedReader.readLine()).startsWith("#")) {
- project.getLogger().warn(line);
- }
+ while ((line = clientBufferedReader.readLine()).startsWith("#")) {
+ project.getLogger().warn(line);
+ }
- project.getLogger().warn("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
- } catch (IOException e) {
- throw new RuntimeException("Failed to read client mappings", e);
+ project.getLogger().warn("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to read client mappings", e);
+ }
}
return Collections.singleton(mappingsFile.toFile());
@@ -160,7 +211,7 @@ public class MojangMappingsDependency implements SelfResolvingDependency {
ClassMapping<?, ?> mojangClassMapping = intermediaryToMojang.getOrCreateClassMapping(inputMappings.getFullObfuscatedName())
.setDeobfuscatedName(namedClass.getFullDeobfuscatedName());
- for (FieldMapping fieldMapping : inputMappings .getFieldMappings()) {
+ for (FieldMapping fieldMapping : inputMappings.getFieldMappings()) {
namedClass.getFieldMapping(fieldMapping.getDeobfuscatedName())
.ifPresent(namedField -> {
mojangClassMapping.getOrCreateFieldMapping(fieldMapping.getSignature())
@@ -168,7 +219,7 @@ public class MojangMappingsDependency implements SelfResolvingDependency {
});
}
- for (MethodMapping methodMapping : inputMappings .getMethodMappings()) {
+ for (MethodMapping methodMapping : inputMappings.getMethodMappings()) {
namedClass.getMethodMapping(methodMapping.getDeobfuscatedSignature())
.ifPresent(namedMethod -> {
mojangClassMapping.getOrCreateMethodMapping(methodMapping.getSignature())
@@ -203,10 +254,26 @@ public class MojangMappingsDependency implements SelfResolvingDependency {
@Override
public String getVersion() {
+ if (extension.getDependencyManager() == null) return "1.0.0";
return extension.getMinecraftProvider().getMinecraftVersion();
}
@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 MojangMappingsDependency) {
return ((MojangMappingsDependency) dependency).extension.getMinecraftProvider().getMinecraftVersion().equals(getVersion());
@@ -216,11 +283,6 @@ public class MojangMappingsDependency implements SelfResolvingDependency {
}
@Override
- public Dependency copy() {
- return new MojangMappingsDependency(project, extension);
- }
-
- @Override
public String getReason() {
return null;
}
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 0032a177..88fd4338 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
@@ -25,21 +25,39 @@
package net.fabricmc.loom.configuration.providers.minecraft;
import java.io.File;
+import java.io.FileInputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+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.util.Arrays;
+import java.util.HashSet;
import java.util.Map;
+import java.util.Set;
import java.util.function.Consumer;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
import com.google.common.collect.ImmutableMap;
import org.gradle.api.Project;
+import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.configuration.DependencyProvider;
import net.fabricmc.loom.configuration.providers.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProvider;
import net.fabricmc.loom.util.Constants;
+import net.fabricmc.loom.util.DownloadUtil;
import net.fabricmc.loom.util.TinyRemapperMappingsHelper;
+import net.fabricmc.loom.util.srg.AtRemapper;
+import net.fabricmc.loom.util.srg.CoreModClassRemapper;
+import net.fabricmc.loom.util.srg.InnerClassRemapper;
+import net.fabricmc.mapping.tree.TinyTree;
+import net.fabricmc.tinyremapper.IMappingProvider;
+import net.fabricmc.tinyremapper.NonClassCopyMode;
import net.fabricmc.tinyremapper.OutputConsumerPath;
import net.fabricmc.tinyremapper.TinyRemapper;
@@ -50,8 +68,10 @@ public class MinecraftMappedProvider extends DependencyProvider {
.put("javax/annotation/concurrent/Immutable", "org/jetbrains/annotations/Unmodifiable")
.build();
+ private File inputJar;
private File minecraftMappedJar;
private File minecraftIntermediaryJar;
+ private File minecraftSrgJar;
private MinecraftProvider minecraftProvider;
@@ -65,11 +85,13 @@ 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();
+
+ if (!minecraftMappedJar.exists() || !getIntermediaryJar().exists() || (getExtension().isForge() && !getSrgJar().exists()) || isRefreshDeps() || isForgeAtDirty) {
if (minecraftMappedJar.exists()) {
minecraftMappedJar.delete();
}
@@ -80,12 +102,22 @@ public class MinecraftMappedProvider extends DependencyProvider {
minecraftIntermediaryJar.delete();
}
+ if (getExtension().isForge() && minecraftSrgJar.exists()) {
+ minecraftSrgJar.delete();
+ }
+
try {
mapMinecraftJar();
} 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()) {
+ minecraftSrgJar.delete();
+ }
+
getExtension().getMappingsProvider().cleanFiles();
throw new RuntimeException("Failed to remap minecraft", t);
}
@@ -98,48 +130,120 @@ public class MinecraftMappedProvider extends DependencyProvider {
addDependencies(dependency, postPopulationScheduler);
}
- private void mapMinecraftJar() throws IOException {
+ private void mapMinecraftJar() throws Exception {
String fromM = "official";
MappingsProvider mappingsProvider = getExtension().getMappingsProvider();
- Path input = minecraftProvider.getMergedJar().toPath();
+ Path input = inputJar.toPath();
Path outputMapped = minecraftMappedJar.toPath();
Path outputIntermediary = minecraftIntermediaryJar.toPath();
+ Path outputSrg = minecraftSrgJar == null ? null : minecraftSrgJar.toPath();
+
+ Path[] libraries = getRemapClasspath(getProject());
+ TinyRemapper remapper = getTinyRemapper();
+ remapper.readClassPath(libraries);
+ remapper.prepareClasses();
- for (String toM : Arrays.asList("named", "intermediary")) {
- Path output = "named".equals(toM) ? outputMapped : outputIntermediary;
+ for (String toM : getExtension().isForge() ? Arrays.asList("named", "intermediary", "srg") : Arrays.asList("named", "intermediary")) {
+ Path output = "named".equals(toM) ? outputMapped : "srg".equals(toM) ? outputSrg : outputIntermediary;
getProject().getLogger().lifecycle(":remapping minecraft (TinyRemapper, " + fromM + " -> " + toM + ")");
Files.deleteIfExists(output);
- TinyRemapper remapper = getTinyRemapper(fromM, toM);
-
try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(output).build()) {
- outputConsumer.addNonClassFiles(input);
- remapper.readClassPath(getRemapClasspath());
+ if (getExtension().isForge()) {
+ outputConsumer.addNonClassFiles(input, NonClassCopyMode.FIX_META_INF, remapper);
+ } else {
+ outputConsumer.addNonClassFiles(input);
+ }
+
+ remapper.replaceMappings(getMappings(input, fromM, toM));
remapper.readInputs(input);
remapper.apply(outputConsumer);
} catch (Exception e) {
+ Files.deleteIfExists(output);
throw new RuntimeException("Failed to remap JAR " + input + " with mappings from " + mappingsProvider.tinyMappings, e);
} finally {
- remapper.finish();
+ remapper.removeInput();
+ }
+
+ if (getExtension().isForge() && !"srg".equals(toM)) {
+ getProject().getLogger().info(":running forge finalising tasks");
+
+ // TODO: Relocate this to its own class
+ try (FileSystem fs = FileSystems.newFileSystem(URI.create("jar:" + output.toUri()), ImmutableMap.of("create", false))) {
+ Path manifestPath = fs.getPath("META-INF", "MANIFEST.MF");
+ Manifest minecraftManifest;
+ Manifest forgeManifest;
+
+ try (InputStream in = Files.newInputStream(manifestPath)) {
+ minecraftManifest = new Manifest(in);
+ }
+
+ try (InputStream in = new FileInputStream(getExtension().getForgeUniversalProvider().getForgeManifest())) {
+ forgeManifest = new Manifest(in);
+ }
+
+ for (Map.Entry<String, Attributes> forgeEntry : forgeManifest.getEntries().entrySet()) {
+ if (forgeEntry.getKey().endsWith("/")) {
+ minecraftManifest.getEntries().put(forgeEntry.getKey(), forgeEntry.getValue());
+ }
+ }
+
+ Files.delete(manifestPath);
+
+ try (OutputStream out = Files.newOutputStream(manifestPath)) {
+ minecraftManifest.write(out);
+ }
+ }
+
+ TinyTree yarnWithSrg = getExtension().getMappingsProvider().getMappingsWithSrg();
+ AtRemapper.remap(getProject().getLogger(), output, yarnWithSrg);
+ CoreModClassRemapper.remapJar(output, yarnWithSrg, getProject().getLogger());
}
}
+
+ remapper.finish();
}
- public TinyRemapper getTinyRemapper(String fromM, String toM) throws IOException {
- return TinyRemapper.newRemapper()
- .withMappings(TinyRemapperMappingsHelper.create(getExtension().getMappingsProvider().getMappings(), fromM, toM, true))
- .withMappings(out -> JSR_TO_JETBRAINS.forEach(out::acceptClass))
+ public TinyRemapper getTinyRemapper() throws IOException {
+ TinyRemapper.Builder builder = TinyRemapper.newRemapper()
.renameInvalidLocals(true)
- .rebuildSourceFilenames(true)
- .build();
+ .ignoreConflicts(getExtension().isForge())
+ .cacheMappings(true)
+ .threads(Runtime.getRuntime().availableProcessors())
+ .logger(getProject().getLogger()::lifecycle)
+ .rebuildSourceFilenames(true);
+
+ if (getExtension().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 builder.build();
}
- public Path[] getRemapClasspath() {
- return getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES).getFiles()
+ public Set<IMappingProvider> getMappings(@Nullable Path fromJar, String fromM, String toM) throws IOException {
+ Set<IMappingProvider> providers = new HashSet<>();
+ providers.add(TinyRemapperMappingsHelper.create(getExtension().isForge() ? getExtension().getMappingsProvider().getMappingsWithSrg() : getExtension().getMappingsProvider().getMappings(), fromM, toM, true));
+
+ if (getExtension().isForge()) {
+ if (fromJar != null) {
+ providers.add(InnerClassRemapper.of(fromJar, getExtension().getMappingsProvider().getMappingsWithSrg(), fromM, toM));
+ }
+ } else {
+ providers.add(out -> JSR_TO_JETBRAINS.forEach(out::acceptClass));
+ }
+
+ return providers;
+ }
+
+ public static Path[] getRemapClasspath(Project project) {
+ return project.getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES).getFiles()
.stream().map(File::toPath).toArray(Path[]::new);
}
@@ -153,7 +257,9 @@ public class MinecraftMappedProvider extends DependencyProvider {
public void initFiles(MinecraftProvider minecraftProvider, MappingsProvider mappingsProvider) {
this.minecraftProvider = minecraftProvider;
minecraftIntermediaryJar = new File(getExtension().getUserCache(), "minecraft-" + getJarVersionString("intermediary") + ".jar");
+ minecraftSrgJar = !getExtension().isForge() ? null : new File(getExtension().getUserCache(), "minecraft-" + getJarVersionString("srg") + ".jar");
minecraftMappedJar = new File(getJarDirectory(getExtension().getUserCache(), "mapped"), "minecraft-" + getJarVersionString("mapped") + ".jar");
+ inputJar = getExtension().isForge() ? mappingsProvider.patchedProvider.getMergedJar() : minecraftProvider.getMergedJar();
}
protected File getJarDirectory(File parentDirectory, String type) {
@@ -161,13 +267,17 @@ public class MinecraftMappedProvider extends DependencyProvider {
}
protected String getJarVersionString(String type) {
- return String.format("%s-%s-%s-%s", minecraftProvider.getMinecraftVersion(), type, getExtension().getMappingsProvider().mappingsName, getExtension().getMappingsProvider().mappingsVersion);
+ return String.format("%s-%s-%s-%s%s", minecraftProvider.getMinecraftVersion(), type, getExtension().getMappingsProvider().mappingsName, getExtension().getMappingsProvider().mappingsVersion, minecraftProvider.getJarSuffix());
}
public File getIntermediaryJar() {
return minecraftIntermediaryJar;
}
+ public File getSrgJar() {
+ return minecraftSrgJar;
+ }
+
public File getMappedJar() {
return minecraftMappedJar;
}
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 931e8672..44a9a3e8 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.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.HashedDownloadUtil;
-import net.fabricmc.loom.util.gradle.ProgressLogger;
public class MinecraftAssetsProvider {
public static void provide(MinecraftProvider minecraftProvider, Project project) throws IOException {
@@ -78,8 +79,8 @@ public class MinecraftAssetsProvider {
HashedDownloadUtil.downloadIfInvalid(new URL(assetIndex.getUrl()), assetsInfo, assetIndex.getSha1(), project.getLogger(), false);
}
- Deque<ProgressLogger> 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.getFileMap();
- for (Map.Entry<String, AssetObject> entry : parent.entrySet()) {
- AssetObject object = entry.getValue();
- String sha1 = object.getHash();
- 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.getHash();
+ 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 ProgressLogger[] progressLogger = new ProgressLogger[1];
+ executor.execute(() -> {
+ String assetName = entry.getKey();
+ int end = assetName.lastIndexOf("/") + 1;
- try {
- HashedDownloadUtil.downloadIfInvalid(new URL(Constants.RESOURCES_BASE + sha1.substring(0, 2) + "/" + sha1), file, sha1, project.getLogger(), true, () -> {
- ProgressLogger logger = loggers.pollFirst();
+ if (end > 0) {
+ assetName = assetName.substring(end);
+ }
- if (logger == null) {
- //Create a new logger if we need one
- progressLogger[0] = ProgressLogger.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(Constants.RESOURCES_BASE + 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(ProgressLogger::completed);
}
}
diff --git a/src/main/java/net/fabricmc/loom/decompilers/fernflower/AbstractFernFlowerDecompiler.java b/src/main/java/net/fabricmc/loom/decompilers/fernflower/AbstractFernFlowerDecompiler.java
index ecc13866..a8844f22 100644
--- a/src/main/java/net/fabricmc/loom/decompilers/fernflower/AbstractFernFlowerDecompiler.java
+++ b/src/main/java/net/fabricmc/loom/decompilers/fernflower/AbstractFernFlowerDecompiler.java
@@ -45,6 +45,7 @@ import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences;
import net.fabricmc.loom.api.decompilers.DecompilationMetadata;
import net.fabricmc.loom.api.decompilers.LoomDecompiler;
+import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.ConsumingOutputStream;
import net.fabricmc.loom.util.OperatingSystem;
@@ -100,7 +101,7 @@ public abstract class AbstractFernFlowerDecompiler implements LoomDecompiler {
progressGroup.started();
ExecResult result = ForkingJavaExec.javaexec(
- project,
+ project.getRootProject().getPlugins().hasPlugin(Constants.PLUGIN_ID) ? project.getRootProject() : project,
spec -> {
spec.setMain(fernFlowerExecutor().getName());
spec.jvmArgs("-Xms200m", "-Xmx3G");
diff --git a/src/main/java/net/fabricmc/loom/decompilers/fernflower/ForkingJavaExec.java b/src/main/java/net/fabricmc/loom/decompilers/fernflower/ForkingJavaExec.java
index f33e491f..90080d69 100644
--- a/src/main/java/net/fabricmc/loom/decompilers/fernflower/ForkingJavaExec.java
+++ b/src/main/java/net/fabricmc/loom/decompilers/fernflower/ForkingJavaExec.java
@@ -61,6 +61,7 @@ public class ForkingJavaExec {
ConfigurationContainer configurations = project.getBuildscript().getConfigurations();
DependencyHandler handler = project.getDependencies();
return configurations.getByName("classpath")
+ .plus(project.getRootProject().getBuildscript().getConfigurations().getByName("classpath"))
.plus(configurations.detachedConfiguration(handler.localGroovy()));
}
diff --git a/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java b/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java
index 8df8e808..34f2dac8 100644
--- a/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java
+++ b/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java
@@ -79,6 +79,7 @@ public abstract class AbstractRunTask extends JavaExec {
args(argsSplit);
setWorkingDir(new File(getProject().getRootDir(), config.runDir));
+ environment(config.envVariables);
super.exec();
}
diff --git a/src/main/java/net/fabricmc/loom/task/GenEclipseRunsTask.java b/src/main/java/net/fabricmc/loom/task/GenEclipseRunsTask.java
index c935f68a..5a3c868b 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 2b7e4139..9eb8650a 100644
--- a/src/main/java/net/fabricmc/loom/task/GenVsCodeProjectTask.java
+++ b/src/main/java/net/fabricmc/loom/task/GenVsCodeProjectTask.java
@@ -28,11 +28,16 @@ import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
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;
@@ -47,9 +52,12 @@ import net.fabricmc.loom.configuration.ide.RunConfigSettings;
public class GenVsCodeProjectTask extends AbstractLoomTask {
@TaskAction
public void genRuns() {
- Project project = getProject();
- LoomGradleExtension extension = getExtension();
- 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();
@@ -60,10 +68,34 @@ public class GenVsCodeProjectTask extends AbstractLoomTask {
if (launchJson.exists()) {
launchJson.delete();
}
+ }
+
+ public static void generate(Project project) {
+ LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class);
+ File projectDir = project.getRootProject().file(".vscode");
+
+ if (!projectDir.exists()) {
+ projectDir.mkdir();
+ }
- VsCodeLaunch launch = new VsCodeLaunch();
+ File launchJson = new File(projectDir, "launch.json");
+ File tasksJson = new File(projectDir, "tasks.json");
- for (RunConfigSettings settings : getExtension().getRunConfigs()) {
+ Gson gson = new GsonBuilder().setPrettyPrinting().create();
+
+ VsCodeLaunch launch;
+
+ 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;
}
@@ -72,7 +104,6 @@ public class GenVsCodeProjectTask extends AbstractLoomTask {
settings.makeRunDir();
}
- Gson gson = new GsonBuilder().setPrettyPrinting().create();
String json = gson.toJson(launch);
try {
@@ -80,6 +111,39 @@ 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 static class VsCodeLaunch {
@@ -87,7 +151,25 @@ public class GenVsCodeProjectTask extends AbstractLoomTask {
public List<VsCodeConfiguration> configurations = new ArrayList<>();
public void add(RunConfig runConfig) {
- configurations.add(new VsCodeConfiguration(runConfig));
+ if (configurations.stream().noneMatch(config -> Objects.equals(config.name, runConfig.configName))) {
+ VsCodeConfiguration configuration = new VsCodeConfiguration(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);
+ }
}
}
@@ -102,6 +184,10 @@ 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) {
this.name = runConfig.configName;
@@ -109,6 +195,24 @@ public class GenVsCodeProjectTask extends AbstractLoomTask {
this.vmArgs = runConfig.vmArgs;
this.args = runConfig.programArgs;
this.cwd = "${workspaceFolder}/" + runConfig.runDir;
+ this.projectName = runConfig.vscodeProjectName;
+ this.env.putAll(runConfig.envVariables);
+ this.tasksBeforeRun.addAll(runConfig.vscodeBeforeRun);
+ }
+ }
+
+ 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/RemapJarTask.java b/src/main/java/net/fabricmc/loom/task/RemapJarTask.java
index 2e69f12a..cf03e57c 100644
--- a/src/main/java/net/fabricmc/loom/task/RemapJarTask.java
+++ b/src/main/java/net/fabricmc/loom/task/RemapJarTask.java
@@ -27,15 +27,33 @@ package net.fabricmc.loom.task;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.Reader;
+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.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonObject;
+import me.shedaniel.architectury.refmapremapper.RefmapRemapper;
+import me.shedaniel.architectury.refmapremapper.remapper.MappingsRemapper;
+import me.shedaniel.architectury.refmapremapper.remapper.ReferenceRemapper;
+import me.shedaniel.architectury.refmapremapper.remapper.Remapper;
+import me.shedaniel.architectury.refmapremapper.remapper.SimpleReferenceRemapper;
import org.gradle.api.Action;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
@@ -47,11 +65,13 @@ import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.TaskAction;
import org.gradle.jvm.tasks.Jar;
import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Nullable;
import org.zeroturnaround.zip.ZipUtil;
import net.fabricmc.loom.LoomGradleExtension;
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.NestedJarPathProvider;
import net.fabricmc.loom.build.nesting.JarNester;
import net.fabricmc.loom.build.nesting.MergedNestedJarProvider;
@@ -60,10 +80,17 @@ import net.fabricmc.loom.build.nesting.NestedJarProvider;
import net.fabricmc.loom.configuration.accesswidener.AccessWidenerJarProcessor;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProvider;
import net.fabricmc.loom.util.Constants;
+import net.fabricmc.loom.util.SourceRemapper;
import net.fabricmc.loom.util.TinyRemapperMappingsHelper;
import net.fabricmc.loom.util.gradle.GradleSupport;
import net.fabricmc.loom.util.ZipReprocessorUtil;
+import net.fabricmc.mapping.tree.ClassDef;
+import net.fabricmc.mapping.tree.FieldDef;
+import net.fabricmc.mapping.tree.MethodDef;
+import net.fabricmc.mapping.tree.TinyTree;
import net.fabricmc.stitch.util.Pair;
+import net.fabricmc.tinyremapper.IMappingProvider;
+import net.fabricmc.tinyremapper.OutputConsumerPath;
import net.fabricmc.tinyremapper.TinyRemapper;
import net.fabricmc.tinyremapper.TinyUtils;
@@ -73,6 +100,8 @@ 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;
public JarRemapper jarRemapper;
private FileCollection classpath;
private final Set<Object> nestedPaths = new LinkedHashSet<>();
@@ -83,6 +112,10 @@ public class RemapJarTask extends Jar {
addNestedDependencies = getProject().getObjects().property(Boolean.class);
addDefaultNestedDependencies = getProject().getObjects().property(Boolean.class);
remapAccessWidener = getProject().getObjects().property(Boolean.class);
+ fromM = getProject().getObjects().property(String.class);
+ toM = getProject().getObjects().property(String.class);
+ fromM.set("named");
+ toM.set(SourceRemapper.intermediary(getProject()));
// false by default, I have no idea why I have to do it for this property and not the other one
remapAccessWidener.set(false);
addDefaultNestedDependencies.set(true);
@@ -100,10 +133,76 @@ public class RemapJarTask extends Jar {
scheduleRemap(singleRemap || getProject().getExtensions().getByType(LoomGradleExtension.class).isRootProject());
if (singleRemap) {
- jarRemapper.remap();
+ jarRemapper.remap(getProject());
}
}
+ private ReferenceRemapper createReferenceRemapper(LoomGradleExtension extension, String from, String to) throws IOException {
+ TinyTree mappings = extension.shouldGenerateSrgTiny() ? extension.getMappingsProvider().getMappingsWithSrg() : extension.getMappingsProvider().getMappings();
+
+ return new SimpleReferenceRemapper(new SimpleReferenceRemapper.Remapper() {
+ @Override
+ @Nullable
+ public String mapClass(String value) {
+ return mappings.getClasses().stream()
+ .filter(classDef -> Objects.equals(classDef.getName(from), value))
+ .findFirst()
+ .map(classDef -> classDef.getName(to))
+ .orElse(null);
+ }
+
+ @Override
+ @Nullable
+ public String mapMethod(@Nullable String className, String methodName, String methodDescriptor) {
+ if (className != null) {
+ Optional<ClassDef> classDef = mappings.getClasses().stream()
+ .filter(c -> Objects.equals(c.getName(from), className))
+ .findFirst();
+
+ if (classDef.isPresent()) {
+ for (MethodDef methodDef : classDef.get().getMethods()) {
+ if (Objects.equals(methodDef.getName(from), methodName) && Objects.equals(methodDef.getDescriptor(from), methodDescriptor)) {
+ return methodDef.getName(to);
+ }
+ }
+ }
+ }
+
+ return mappings.getClasses().stream()
+ .flatMap(classDef -> classDef.getMethods().stream())
+ .filter(methodDef -> Objects.equals(methodDef.getName(from), methodName) && Objects.equals(methodDef.getDescriptor(from), methodDescriptor))
+ .findFirst()
+ .map(methodDef -> methodDef.getName(to))
+ .orElse(null);
+ }
+
+ @Override
+ @Nullable
+ public String mapField(@Nullable String className, String fieldName, String fieldDescriptor) {
+ if (className != null) {
+ Optional<ClassDef> classDef = mappings.getClasses().stream()
+ .filter(c -> Objects.equals(c.getName(from), className))
+ .findFirst();
+
+ if (classDef.isPresent()) {
+ for (FieldDef fieldDef : classDef.get().getFields()) {
+ if (Objects.equals(fieldDef.getName(from), fieldName) && Objects.equals(fieldDef.getDescriptor(from), fieldDescriptor)) {
+ return fieldDef.getName(to);
+ }
+ }
+ }
+ }
+
+ return mappings.getClasses().stream()
+ .flatMap(classDef -> classDef.getFields().stream())
+ .filter(fieldDef -> Objects.equals(fieldDef.getName(from), fieldName) && Objects.equals(fieldDef.getDescriptor(from), fieldDescriptor))
+ .findFirst()
+ .map(fieldDef -> fieldDef.getName(to))
+ .orElse(null);
+ }
+ });
+ }
+
public void scheduleRemap(boolean isMainRemapTask) throws Throwable {
Project project = getProject();
LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class);
@@ -116,24 +215,27 @@ public class RemapJarTask extends Jar {
MappingsProvider mappingsProvider = extension.getMappingsProvider();
- String fromM = "named";
- String toM = "intermediary";
+ String fromM = this.fromM.get();
+ String toM = this.toM.get();
if (isMainRemapTask) {
jarRemapper.addToClasspath(getRemapClasspath());
- jarRemapper.addMappings(TinyRemapperMappingsHelper.create(mappingsProvider.getMappings(), fromM, toM, false));
+ jarRemapper.addMappings(TinyRemapperMappingsHelper.create(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,6 +268,14 @@ public class RemapJarTask extends Jar {
project.getLogger().debug("Transformed mixin reference maps in output JAR!");
}
+ if (!toM.equals("intermediary")) {
+ try {
+ remapRefmap(extension, output, "intermediary", toM);
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to remap refmap jar", e);
+ }
+ }
+
if (getAddNestedDependencies().getOrElse(false)) {
JarNester.nestJars(nestedJarProvider.provide(), output.toFile(), project.getLogger());
}
@@ -185,7 +295,45 @@ public class RemapJarTask extends Jar {
});
}
+ private void remapRefmap(LoomGradleExtension extension, Path output, String from, String to) throws IOException {
+ try (FileSystem fs = FileSystems.newFileSystem(URI.create("jar:" + output.toUri()), ImmutableMap.of("create", false))) {
+ Path refmapPath = fs.getPath(extension.getRefmapName());
+
+ if (Files.exists(refmapPath)) {
+ try (Reader refmapReader = Files.newBufferedReader(refmapPath, StandardCharsets.UTF_8)) {
+ Gson gson = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create();
+ JsonObject refmapElement = gson.fromJson(refmapReader, JsonObject.class);
+ refmapElement = RefmapRemapper.remap(new Remapper() {
+ ReferenceRemapper remapper = createReferenceRemapper(extension, from, to);
+
+ @Override
+ @Nullable
+ public MappingsRemapper remapMappings() {
+ return className -> remapper;
+ }
+
+ @Override
+ @Nullable
+ public Map.Entry<String, @Nullable MappingsRemapper> remapMappingsData(String data) {
+ if (Objects.equals(data, "named:intermediary")) {
+ return new AbstractMap.SimpleEntry<>(Objects.equals(to, "srg") ? "searge" : data, remapMappings());
+ }
+
+ return null;
+ }
+ }, refmapElement);
+ Files.delete(refmapPath);
+ Files.write(refmapPath, gson.toJson(refmapElement).getBytes(StandardCharsets.UTF_8));
+ }
+ }
+ }
+ }
+
private NestedJarProvider getNestedJarProvider() {
+ if (!getProject().getExtensions().getByType(LoomGradleExtension.class).supportsInclude()) {
+ return EmptyNestedJarProvider.INSTANCE;
+ }
+
Configuration includeConfiguration = getProject().getConfigurations().getByName(Constants.Configurations.INCLUDE);
if (!addDefaultNestedDependencies.getOrElse(true)) {
@@ -204,6 +352,57 @@ public class RemapJarTask extends Jar {
);
}
+ private IMappingProvider remapToSrg(LoomGradleExtension extension, IMappingProvider parent, String from, String to) throws IOException {
+ TinyTree mappings = 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;
@@ -258,4 +457,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 9548ad7f..4b7f6635 100644
--- a/src/main/java/net/fabricmc/loom/task/RemapSourcesJarTask.java
+++ b/src/main/java/net/fabricmc/loom/task/RemapSourcesJarTask.java
@@ -37,18 +37,25 @@ import net.fabricmc.loom.util.SourceRemapper;
public class RemapSourcesJarTask extends AbstractLoomTask {
private Object input;
private Object output;
- private String direction = "intermediary";
+ private String from = "named";
+ private String direction;
private SourceRemapper sourceRemapper = null;
private boolean preserveFileTimestamps = true;
private boolean reproducibleFileOrder = false;
public RemapSourcesJarTask() {
+ this.direction = SourceRemapper.intermediary(getProject());
}
@TaskAction
public void remap() throws Exception {
if (sourceRemapper == null) {
- SourceRemapper.remapSources(getProject(), getInput(), getOutput(), direction.equals("named"), reproducibleFileOrder, preserveFileTimestamps);
+ if (from.equals(direction)) {
+ SourceRemapper.remapSources(getProject(), getInput(), getOutput(),
+ direction.equals("named") ? SourceRemapper.intermediary(getProject()) : "named", direction, reproducibleFileOrder, preserveFileTimestamps);
+ } else {
+ SourceRemapper.remapSources(getProject(), getInput(), getOutput(), from, direction, reproducibleFileOrder, preserveFileTimestamps);
+ }
} else {
sourceRemapper.scheduleRemapSources(getInput(), getOutput(), reproducibleFileOrder, preserveFileTimestamps);
}
@@ -75,6 +82,11 @@ public class RemapSourcesJarTask extends AbstractLoomTask {
}
@Input
+ public String getSourceNamespace() {
+ return from;
+ }
+
+ @Input
public String getTargetNamespace() {
return direction;
}
@@ -87,6 +99,10 @@ public class RemapSourcesJarTask extends AbstractLoomTask {
this.output = output;
}
+ public void setSourceNamespace(String value) {
+ this.from = value;
+ }
+
public void setTargetNamespace(String value) {
this.direction = value;
}
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..2c9798dd
--- /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) 2016, 2017, 2018 FabricMC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package net.fabricmc.loom.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 = project.getExtensions().getByType(LoomGradleExtension.class);
+ return RunConfig.runConfig(project, extension.getRunConfigs().getByName("data"));
+ });
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/Checksum.java b/src/main/java/net/fabricmc/loom/util/Checksum.java
index 1b38a051..23690905 100644
--- a/src/main/java/net/fabricmc/loom/util/Checksum.java
+++ b/src/main/java/net/fabricmc/loom/util/Checksum.java
@@ -26,6 +26,7 @@ package net.fabricmc.loom.util;
import java.io.File;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import com.google.common.hash.HashCode;
import com.google.common.hash.Hashing;
@@ -43,8 +44,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();
}
@@ -60,4 +62,9 @@ public class Checksum {
throw new RuntimeException("Failed to get file hash");
}
}
+
+ 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 7ca96591..b44c92a5 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.util.gradle.GradleSupport;
public class Constants {
+ public static final String PLUGIN_ID = "forgified-fabric-loom";
public static final String LIBRARIES_BASE = "https://libraries.minecraft.net/";
public static final String RESOURCES_BASE = "http://resources.download.minecraft.net/";
public static final String VERSION_MANIFESTS = "https://launchermeta.mojang.com/mc/game/version_manifest_v2.json";
@@ -77,6 +78,13 @@ 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";
+ public static final String FORGE_DEPENDENCIES = "forgeDependencies";
@Deprecated // Not to be used in gradle 7+
public static final String COMPILE = "compile";
public static final String MAPPING_CONSTANTS = "mappingsConstants";
@@ -94,6 +102,7 @@ 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.
private Dependencies() {
}
@@ -106,6 +115,7 @@ 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 = "19.0.0";
+ public static final String JAVAX_ANNOTATIONS = "3.0.2";
private Versions() {
}
@@ -137,4 +147,11 @@ public class Constants {
private Knot() {
}
}
+
+ public static final class ForgeUserDev {
+ public static final String LAUNCH_TESTING = "net.minecraftforge.userdev.LaunchTesting";
+
+ private ForgeUserDev() {
+ }
+ }
}
diff --git a/src/main/java/net/fabricmc/loom/util/DownloadUtil.java b/src/main/java/net/fabricmc/loom/util/DownloadUtil.java
index 90131b4c..1da65ae5 100644
--- a/src/main/java/net/fabricmc/loom/util/DownloadUtil.java
+++ b/src/main/java/net/fabricmc/loom/util/DownloadUtil.java
@@ -46,8 +46,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);
}
/**
@@ -59,7 +59,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) {
@@ -100,7 +100,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();
@@ -132,6 +132,8 @@ public class DownloadUtil {
saveETag(to, eTag, logger);
}
+
+ return true;
}
/**
@@ -220,5 +222,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
new file mode 100644
index 00000000..cb37d2f3
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/FileSystemUtil.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2016 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.loom.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystemAlreadyExistsException;
+import java.nio.file.FileSystems;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+
+public final class FileSystemUtil {
+ public static class FileSystemDelegate implements AutoCloseable {
+ private final FileSystem fileSystem;
+ private final boolean owner;
+
+ public FileSystemDelegate(FileSystem fileSystem, boolean owner) {
+ this.fileSystem = fileSystem;
+ this.owner = owner;
+ }
+
+ public FileSystem get() {
+ return fileSystem;
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (owner) {
+ fileSystem.close();
+ }
+ }
+ }
+
+ private FileSystemUtil() {
+ }
+
+ private static final Map<String, String> jfsArgsCreate = new HashMap<>();
+ private static final Map<String, String> jfsArgsEmpty = new HashMap<>();
+
+ static {
+ jfsArgsCreate.put("create", "true");
+ }
+
+ public static FileSystemDelegate getJarFileSystem(File file, boolean create) throws IOException {
+ return getJarFileSystem(file.toURI(), create);
+ }
+
+ public static FileSystemDelegate getJarFileSystem(Path path, boolean create) throws IOException {
+ return getJarFileSystem(path.toUri(), create);
+ }
+
+ public static FileSystemDelegate getJarFileSystem(URI uri, boolean create) throws IOException {
+ URI jarUri;
+
+ try {
+ jarUri = new URI("jar:" + uri.getScheme(), uri.getHost(), uri.getPath(), uri.getFragment());
+ } catch (URISyntaxException e) {
+ throw new IOException(e);
+ }
+
+ try {
+ return new FileSystemDelegate(FileSystems.newFileSystem(jarUri, create ? jfsArgsCreate : jfsArgsEmpty), true);
+ } catch (FileSystemAlreadyExistsException e) {
+ return new FileSystemDelegate(FileSystems.getFileSystem(jarUri), false);
+ }
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/HashedDownloadUtil.java b/src/main/java/net/fabricmc/loom/util/HashedDownloadUtil.java
index 8f1f2b25..a03126d1 100644
--- a/src/main/java/net/fabricmc/loom/util/HashedDownloadUtil.java
+++ b/src/main/java/net/fabricmc/loom/util/HashedDownloadUtil.java
@@ -41,20 +41,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/JarUtil.java b/src/main/java/net/fabricmc/loom/util/JarUtil.java
new file mode 100644
index 00000000..29eb996c
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/JarUtil.java
@@ -0,0 +1,50 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2016, 2017, 2018 FabricMC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package net.fabricmc.loom.util;
+
+import java.io.File;
+import java.io.IOException;
+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 com.google.common.collect.ImmutableMap;
+
+/**
+ * Working with jars.
+ *
+ * @author Juuz
+ */
+public final class JarUtil {
+ public static void extractFile(File jar, String filePath, File target) throws IOException {
+ try (FileSystem fs = FileSystems.newFileSystem(URI.create("jar:" + jar.toURI()), ImmutableMap.of("create", false))) {
+ Path targetPath = target.toPath();
+ Files.deleteIfExists(targetPath);
+ Files.copy(fs.getPath(filePath), targetPath);
+ }
+ }
+}
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..b7803c34
--- /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) 2016, 2017, 2018 FabricMC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package net.fabricmc.loom.util;
+
+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/SourceRemapper.java b/src/main/java/net/fabricmc/loom/util/SourceRemapper.java
index e673d0a7..f9573bf5 100644
--- a/src/main/java/net/fabricmc/loom/util/SourceRemapper.java
+++ b/src/main/java/net/fabricmc/loom/util/SourceRemapper.java
@@ -52,18 +52,29 @@ import net.fabricmc.stitch.util.StitchUtil;
public class SourceRemapper {
private final Project project;
- private final boolean toNamed;
+ private String from;
+ private String to;
private final List<Consumer<ProgressLogger>> 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 = project.getExtensions().getByType(LoomGradleExtension.class);
+ 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();
}
@@ -104,7 +115,7 @@ public class SourceRemapper {
}
private void remapSourcesInner(File source, File destination) throws Exception {
- project.getLogger().info(":remapping source jar");
+ project.getLogger().info(":remapping source jar " + source.getName() + " from " + from + " to " + to);
Mercury mercury = getMercuryInstance();
if (source.equals(destination)) {
@@ -165,18 +176,27 @@ public class SourceRemapper {
LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class);
MappingsProvider 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 {
- TinyTree m = mappingsProvider.getMappings();
- project.getLogger().info(":loading " + (toNamed ? "intermediary -> named" : "named -> intermediary") + " source mappings");
- return new TinyMappingsReader(m, toNamed ? "intermediary" : "named", toNamed ? "named" : "intermediary").read();
+ TinyTree m = 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();
@@ -189,6 +209,10 @@ 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());
+ }
+
Dependency annotationDependency = extension.getDependencyManager().getProvider(LaunchProvider.class).annotationDependency;
Set<File> files = project.getConfigurations().getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME)
.files(annotationDependency);
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..0e58cfa5
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/ThreadingUtils.java
@@ -0,0 +1,124 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2016, 2017, 2018 FabricMC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package net.fabricmc.loom.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+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;
+
+public class ThreadingUtils {
+ 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.min(jobs.size(), Runtime.getRuntime().availableProcessors() / 2));
+ 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.min(jobs.size(), Runtime.getRuntime().availableProcessors() / 2));
+ 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;
+ }
+}
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..37d648d8
--- /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) 2016, 2017, 2018 FabricMC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package net.fabricmc.loom.util.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..f17364b2
--- /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) 2016, 2017, 2018 FabricMC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package net.fabricmc.loom.util.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..0974d95f
--- /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) 2016, 2017, 2018 FabricMC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package net.fabricmc.loom.util.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..ff53aace
--- /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) 2016, 2017, 2018 FabricMC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package net.fabricmc.loom.util.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..467c15a4
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/srg/AtRemapper.java
@@ -0,0 +1,134 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2016, 2017, 2018 FabricMC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package net.fabricmc.loom.util.srg;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.UnaryOperator;
+import java.util.zip.ZipEntry;
+
+import org.apache.logging.log4j.util.Strings;
+import org.gradle.api.logging.Logger;
+import org.zeroturnaround.zip.ZipUtil;
+import org.zeroturnaround.zip.transform.StringZipEntryTransformer;
+import org.zeroturnaround.zip.transform.ZipEntryTransformerEntry;
+
+import net.fabricmc.loom.util.function.CollectionUtil;
+import net.fabricmc.mapping.tree.TinyTree;
+
+/**
+ * Remaps AT classes from SRG to Yarn.
+ *
+ * @author Juuz
+ */
+public final class AtRemapper {
+ public static void remap(Logger logger, Path jar, TinyTree mappings) throws IOException {
+ ZipUtil.transformEntries(jar.toFile(), new ZipEntryTransformerEntry[] {(new ZipEntryTransformerEntry("META-INF/accesstransformer.cfg", new StringZipEntryTransformer() {
+ @Override
+ protected String transform(ZipEntry zipEntry, String input) {
+ String[] lines = input.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("#") || Strings.isBlank(line)) {
+ 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));
+ }
+
+ return String.join("\n", output);
+ }
+ }))});
+ }
+
+ 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..9fb93079
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/srg/CoreModClassRemapper.java
@@ -0,0 +1,116 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2016, 2017, 2018 FabricMC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package net.fabricmc.loom.util.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.apache.logging.log4j.util.Strings;
+import org.gradle.api.logging.Logger;
+
+import net.fabricmc.loom.util.function.CollectionUtil;
+import net.fabricmc.mapping.tree.TinyTree;
+
+/**
+ * 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, TinyTree 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, TinyTree 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(Strings.LINE_SEPARATOR, 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..893dfe1b
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/srg/InnerClassRemapper.java
@@ -0,0 +1,78 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2016, 2017, 2018 FabricMC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package net.fabricmc.loom.util.srg;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.stream.Collectors;
+
+import org.zeroturnaround.zip.ZipUtil;
+
+import net.fabricmc.mapping.tree.ClassDef;
+import net.fabricmc.mapping.tree.TinyTree;
+import net.fabricmc.tinyremapper.IMappingProvider;
+
+public class InnerClassRemapper {
+ public static IMappingProvider of(Path fromJar, TinyTree mappingsWithSrg, String from, String to) throws IOException {
+ return sink -> {
+ remapInnerClass(fromJar, mappingsWithSrg, from, to, sink::acceptClass);
+ };
+ }
+
+ private static void remapInnerClass(Path fromJar, TinyTree mappingsWithSrg, String from, String to, BiConsumer<String, String> action) {
+ try (InputStream inputStream = Files.newInputStream(fromJar)) {
+ Map<String, String> availableClasses = mappingsWithSrg.getClasses().stream()
+ .collect(Collectors.groupingBy(classDef -> classDef.getName(from),
+ Collectors.<ClassDef, String>reducing(
+ null,
+ classDef -> classDef.getName(to),
+ (first, last) -> last
+ ))
+ );
+ ZipUtil.iterate(inputStream, (in, zipEntry) -> {
+ if (!zipEntry.isDirectory() && zipEntry.getName().contains("$") && zipEntry.getName().endsWith(".class")) {
+ String className = zipEntry.getName().substring(0, zipEntry.getName().length() - 6);
+
+ 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)) {
+ action.accept(className, remappedName);
+ }
+ }
+ }
+ });
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
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..7639eb00
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/srg/MCPReader.java
@@ -0,0 +1,347 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2016, 2017, 2018 FabricMC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package net.fabricmc.loom.util.srg;
+
+import java.io.IOException;
+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.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import au.com.bytecode.opencsv.CSVReader;
+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.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<>();
+ injectMcp(mcpJar, intermediaryToMCPMap, intermediaryToDocsMap, intermediaryToParamsMap);
+
+ 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 (TSrgReader reader = new TSrgReader(Files.newBufferedReader(srgTsrgPath, StandardCharsets.UTF_8))) {
+ MappingSet mappingSet = reader.read();
+
+ for (TopLevelClassMapping classMapping : mappingSet.getTopLevelClassMappings()) {
+ appendClass(tokens, classMapping);
+ }
+ }
+
+ return tokens;
+ }
+
+ private void injectMcp(Path mcpJar, Map<String, String> intermediaryToSrgMap, Map<String, String[]> intermediaryToDocsMap, Map<String, Map<Integer, String>> intermediaryToParamsMap)
+ throws IOException {
+ 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, 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 static class MemberToken {
+ private final TokenType type;
+ @Nullable
+ private final MemberToken owner;
+ private final String name;
+ @Nullable
+ private final String descriptor;
+
+ MemberToken(TokenType type, @Nullable MemberToken owner, String name, @Nullable String descriptor) {
+ this.type = type;
+ this.owner = owner;
+ this.name = name;
+ this.descriptor = 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);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ MemberToken that = (MemberToken) o;
+ return type == that.type && name.equals(that.name) && Objects.equals(descriptor, that.descriptor) && Objects.equals(owner, that.owner);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type, name, descriptor, owner);
+ }
+ }
+
+ 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..ada083ce
--- /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) 2016, 2017, 2018 FabricMC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package net.fabricmc.loom.util.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..72a482cf
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java
@@ -0,0 +1,108 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2016, 2017, 2018 FabricMC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package net.fabricmc.loom.util.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.util.Arrays;
+import java.util.Set;
+import java.util.jar.JarOutputStream;
+import java.util.stream.Collectors;
+import java.util.zip.ZipEntry;
+
+import org.apache.commons.io.IOUtils;
+import org.gradle.api.Project;
+import org.zeroturnaround.zip.ZipUtil;
+
+import net.fabricmc.loom.LoomGradleExtension;
+import net.fabricmc.loom.configuration.providers.mappings.MappingsProvider;
+
+public class SpecialSourceExecutor {
+ public static Path produceSrgJar(Project project, MappingsProvider provider, String side, File specialSourceJar, Path officialJar, Path srgPath)
+ throws Exception {
+ Set<String> filter = Files.readAllLines(srgPath, StandardCharsets.UTF_8).stream()
+ .filter(s -> !s.startsWith("\t"))
+ .map(s -> s.split(" ")[0] + ".class")
+ .collect(Collectors.toSet());
+ LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class);
+ Path stripped = extension.getProjectBuildCache().toPath().resolve(officialJar.getFileName().toString().substring(0, officialJar.getFileName().toString().length() - 4) + "-filtered.jar");
+ Files.deleteIfExists(stripped);
+
+ try (JarOutputStream output = new JarOutputStream(Files.newOutputStream(stripped))) {
+ ZipUtil.iterate(officialJar.toFile(), (in, zipEntry) -> {
+ if (filter.contains(zipEntry.getName())) {
+ output.putNextEntry((ZipEntry) zipEntry.clone());
+ IOUtils.write(IOUtils.toByteArray(in), output);
+ output.closeEntry();
+ }
+ });
+ }
+
+ Path output = extension.getProjectBuildCache().toPath().resolve(officialJar.getFileName().toString().substring(0, officialJar.getFileName().toString().length() - 4) + "-srg-output.jar");
+ Files.deleteIfExists(output);
+
+ String[] args = new String[] {
+ "--in-jar",
+ stripped.toAbsolutePath().toString(),
+ "--out-jar",
+ output.toAbsolutePath().toString(),
+ "--srg-in",
+ srgPath.toAbsolutePath().toString()
+ };
+
+ project.getLogger().lifecycle(":remapping minecraft (SpecialSource, " + side + ", official -> srg)");
+
+ Path workingDir = tmpDir();
+
+ project.javaexec(spec -> {
+ spec.setArgs(Arrays.asList(args));
+ spec.setClasspath(project.files(specialSourceJar));
+ spec.workingDir(workingDir.toFile());
+ spec.setMain("net.md_5.specialsource.SpecialSource");
+ spec.setStandardOutput(System.out);
+ spec.setErrorOutput(System.out);
+ }).rethrowFailure().assertNormalExitValue();
+
+ 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..9f9fbe7d
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java
@@ -0,0 +1,181 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2016, 2017, 2018 FabricMC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package net.fabricmc.loom.util.srg;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+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.loom.util.function.CollectionUtil;
+import net.fabricmc.mapping.tree.ClassDef;
+import net.fabricmc.mapping.tree.FieldDef;
+import net.fabricmc.mapping.tree.MethodDef;
+import net.fabricmc.mapping.tree.TinyMappingFactory;
+import net.fabricmc.mapping.tree.TinyTree;
+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.TinyHeader;
+import net.fabricmc.stitch.commands.tinyv2.TinyMethod;
+import net.fabricmc.stitch.commands.tinyv2.TinyV2Writer;
+
+/**
+ * Utilities for merging SRG mappings.
+ *
+ * @author Juuz
+ */
+public final class SrgMerger {
+ /**
+ * Merges SRG mappings with a tiny mappings tree through the obf names.
+ *
+ * @param srg the SRG file in .tsrg format
+ * @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(Path srg, Path tiny, Path out, boolean lenient) throws IOException, MappingException {
+ MappingSet arr;
+ TinyTree foss;
+
+ try (TSrgReader reader = new TSrgReader(Files.newBufferedReader(srg))) {
+ arr = reader.read();
+ }
+
+ try (BufferedReader reader = Files.newBufferedReader(tiny)) {
+ foss = TinyMappingFactory.loadWithDetection(reader);
+ }
+
+ List<String> namespaces = new ArrayList<>(foss.getMetadata().getNamespaces());
+ namespaces.add(1, "srg");
+
+ if (!"official".equals(namespaces.get(0))) {
+ throw new MappingException("Mapping file " + tiny + " does not have the 'official' namespace as the default!");
+ }
+
+ TinyHeader header = new TinyHeader(namespaces, 2, 0, Collections.emptyMap());
+
+ List<TinyClass> classes = new ArrayList<>();
+
+ for (TopLevelClassMapping klass : arr.getTopLevelClassMappings()) {
+ classToTiny(foss, namespaces, klass, classes::add, lenient);
+ }
+
+ TinyFile file = new TinyFile(header, classes);
+ TinyV2Writer.write(file, out);
+ }
+
+ private static void classToTiny(TinyTree foss, List<String> namespaces, ClassMapping<?, ?> klass, Consumer<TinyClass> classConsumer, boolean lenient) {
+ String obf = klass.getFullObfuscatedName();
+ String srg = klass.getFullDeobfuscatedName();
+ ClassDef classDef = foss.getDefaultNamespaceClassMap().get(obf);
+
+ if (classDef == null) {
+ if (lenient) {
+ return;
+ } else {
+ throw new MappingException("Missing class: " + obf + " (srg: " + srg + ")");
+ }
+ }
+
+ List<String> classNames = CollectionUtil.map(
+ namespaces,
+ namespace -> "srg".equals(namespace) ? srg : classDef.getName(namespace)
+ );
+
+ List<TinyMethod> methods = new ArrayList<>();
+ List<TinyField> fields = new ArrayList<>();
+
+ for (MethodMapping method : klass.getMethodMappings()) {
+ MethodDef def = CollectionUtil.find(
+ classDef.getMethods(),
+ m -> m.getName("official").equals(method.getObfuscatedName()) && m.getDescriptor("official").equals(method.getObfuscatedDescriptor())
+ ).orElse(nullOrThrow(lenient, () -> new MappingException("Missing method: " + method.getFullObfuscatedName() + " (srg: " + method.getFullDeobfuscatedName() + ")")));
+
+ if (def == null) continue;
+
+ List<String> methodNames = CollectionUtil.map(
+ namespaces,
+ namespace -> "srg".equals(namespace) ? method.getDeobfuscatedName() : def.getName(namespace)
+ );
+
+ methods.add(new TinyMethod(
+ def.getDescriptor("official"), methodNames,
+ /* parameters */ Collections.emptyList(),
+ /* locals */ Collections.emptyList(),
+ /* comments */ Collections.emptyList()
+ ));
+ }
+
+ for (FieldMapping field : klass.getFieldMappings()) {
+ FieldDef def = CollectionUtil.find(
+ classDef.getFields(),
+ f -> f.getName("official").equals(field.getObfuscatedName())
+ ).orElse(nullOrThrow(lenient, () -> new MappingException("Missing field: " + field.getFullObfuscatedName() + " (srg: " + field.getFullDeobfuscatedName() + ")")));
+
+ if (def == null) continue;
+
+ List<String> fieldNames = CollectionUtil.map(
+ namespaces,
+ namespace -> "srg".equals(namespace) ? field.getDeobfuscatedName() : def.getName(namespace)
+ );
+
+ fields.add(new TinyField(def.getDescriptor("official"), fieldNames, Collections.emptyList()));
+ }
+
+ TinyClass tinyClass = new TinyClass(classNames, methods, fields, Collections.emptyList());
+ classConsumer.accept(tinyClass);
+
+ for (InnerClassMapping innerKlass : klass.getInnerClassMappings()) {
+ classToTiny(foss, namespaces, innerKlass, classConsumer, lenient);
+ }
+ }
+
+ @Nullable
+ private static <T, X extends Exception> T nullOrThrow(boolean lenient, 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..cdace98a
--- /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) 2016, 2017, 2018 FabricMC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package net.fabricmc.loom.util.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.mapping.tree.TinyTree;
+
+public class SrgNamedWriter {
+ public static void writeTo(Logger logger, Path srgFile, TinyTree 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/resources/idea_run_config_template.xml b/src/main/resources/idea_run_config_template.xml
index 0891bc84..6b71bd71 100644
--- a/src/main/resources/idea_run_config_template.xml
+++ b/src/main/resources/idea_run_config_template.xml
@@ -1,5 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="%NAME%" type="Application" factoryName="Application">
+ %ENVS%
<option name="MAIN_CLASS_NAME" value="%MAIN_CLASS%" />
<module name="%IDEA_MODULE%" />
<option name="PROGRAM_PARAMETERS" value="%PROGRAM_ARGS%" />
diff --git a/src/main/resources/log4j2.fabric.xml b/src/main/resources/log4j2.fabric.xml
index fcfd27b5..1feeddd6 100644
--- a/src/main/resources/log4j2.fabric.xml
+++ b/src/main/resources/log4j2.fabric.xml
@@ -52,6 +52,7 @@
</Appenders>
<Loggers>
<Logger level="${sys:fabric.log.level:-info}" name="net.minecraft"/>
+ <Logger level="warn" name="cpw.mods.modlauncher.ClassTransformer"/>
<Root level="all">
<AppenderRef ref="DebugFile" level="${sys:fabric.log.debug.level:-debug}"/>
<AppenderRef ref="SysOut" level="${sys:fabric.log.level:-info}"/>
diff --git a/src/test/groovy/net/fabricmc/loom/GroovyXmlUtilTest.groovy b/src/test/groovy/net/fabricmc/loom/GroovyXmlUtilTest.groovy
new file mode 100644
index 00000000..a379b727
--- /dev/null
+++ b/src/test/groovy/net/fabricmc/loom/GroovyXmlUtilTest.groovy
@@ -0,0 +1,74 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2016, 2017, 2018 FabricMC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package net.fabricmc.loom
+
+import groovy.xml.QName
+import net.fabricmc.loom.util.GroovyXmlUtil
+import spock.lang.Specification
+
+class GroovyXmlUtilTest extends Specification {
+ def "getOrCreateNode finds existing node"() {
+ when:
+ def xmlTree = new XmlParser().parseText(text)
+ def existingNode = xmlTree[innerName]
+ def actualNode = GroovyXmlUtil.getOrCreateNode(xmlTree, innerName)
+
+ then:
+ existingNode.text() == actualNode.text()
+
+ where:
+ innerName | text
+ "bar" | "<foo><bar>inner content to ensure correct</bar></foo>"
+ "dependencies" | "<project xmlns=\"http://maven.apache.org/POM/4.0.0\"><dependencies>inner content to ensure correct</dependencies></project>"
+ }
+
+ def "getOrCreateNode creates a node if needed"() {
+ when:
+ def xmlTree = new XmlParser().parseText(text)
+ def actualNode = GroovyXmlUtil.getOrCreateNode(xmlTree, innerName)
+
+ then:
+ xmlTree[QName.valueOf(actualNode.name().toString())] != null
+
+ where:
+ innerName | text
+ "bar" | "<foo></foo>"
+ "dependencies" | "<project xmlns=\"http://maven.apache.org/POM/4.0.0\"></project>"
+ }
+
+ def "getNode finds existing node"() {
+ when:
+ def xmlTree = new XmlParser().parseText(text)
+ def actualNode = GroovyXmlUtil.getNode(xmlTree, innerName)
+
+ then:
+ actualNode.isPresent()
+
+ where:
+ innerName | text
+ "bar" | "<foo><bar>inner content to ensure correct</bar></foo>"
+ "dependencies" | "<project xmlns=\"http://maven.apache.org/POM/4.0.0\"><dependencies>inner content to ensure correct</dependencies></project>"
+ }
+}