Merge branch '0.11.0-interf' into dev/0.11.0

# Conflicts:
#	.github/workflows/test.yml
#	build.gradle
#	src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java
#	src/main/java/net/fabricmc/loom/build/JarRemapper.java
#	src/main/java/net/fabricmc/loom/build/MixinRefmapHelper.java
#	src/main/java/net/fabricmc/loom/build/ModCompileRemapper.java
#	src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java
#	src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java
#	src/main/java/net/fabricmc/loom/configuration/JarManifestConfiguration.java
#	src/main/java/net/fabricmc/loom/configuration/RemapConfiguration.java
#	src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java
#	src/main/java/net/fabricmc/loom/configuration/ide/SetupIntelijRunConfigs.java
#	src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java
#	src/main/java/net/fabricmc/loom/configuration/providers/MinecraftProviderImpl.java
#	src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java
#	src/main/java/net/fabricmc/loom/extension/MixinExtensionImpl.java
#	src/main/java/net/fabricmc/loom/task/LoomTasks.java
#	src/main/java/net/fabricmc/loom/task/RemapJarTask.java
#	src/main/java/net/fabricmc/loom/task/RemapSourcesJarTask.java
#	src/main/java/net/fabricmc/loom/util/Constants.java
#	src/test/groovy/net/fabricmc/loom/test/unit/layeredmappings/LayeredMappingSpecBuilderTest.groovy
This commit is contained in:
shedaniel
2022-01-06 15:28:38 +08:00
96 changed files with 2873 additions and 2124 deletions

View File

@@ -13,7 +13,7 @@ jobs:
steps:
- uses: actions/checkout@v2
- uses: gradle/wrapper-validation-action@v1
- run: gradle build check -x test --stacktrace
- run: gradle build check -x test --stacktrace --warning-mode fail
# This job is used to feed the test matrix of next job to allow the tests to run in parallel
prepare_test_matrix:
@@ -27,7 +27,7 @@ jobs:
steps:
- uses: actions/checkout@v2
- run: gradle writeActionsTestMatrix --stacktrace
- run: gradle writeActionsTestMatrix --stacktrace --warning-mode fail
- id: set-matrix
run: echo "::set-output name=matrix::$(cat build/test_matrix.json)"
@@ -50,7 +50,7 @@ jobs:
steps:
- uses: actions/checkout@v2
- run: gradle test --tests ${{ matrix.test }} --stacktrace
- run: gradle test --tests ${{ matrix.test }} --stacktrace --warning-mode fail
env:
TEST_WARNING_MODE: fail
@@ -77,7 +77,7 @@ jobs:
uses: actions/setup-java@v1
with:
java-version: ${{ matrix.java }}
- run: ./gradlew test --tests ${{ matrix.test }} --stacktrace
- run: ./gradlew test --tests ${{ matrix.test }} --stacktrace --warning-mode fail
env:
TEST_WARNING_MODE: fail

View File

@@ -207,8 +207,8 @@ jacoco {
jacocoTestReport {
dependsOn test
reports {
xml.enabled false
csv.enabled false
xml.required = false
csv.required = false
html.destination file("${buildDir}/jacocoHtml")
}
}
@@ -220,6 +220,7 @@ test {
}
import me.shedaniel.javaversionbridge.BridgeTransformingTask
import org.gradle.util.GradleVersion
import org.w3c.dom.Document
import org.w3c.dom.Element
import org.w3c.dom.Node
@@ -338,6 +339,27 @@ tasks.named('wrapper') {
distributionType = Wrapper.DistributionType.ALL
}
/**
* Run this task to download the gradle sources next to the api jar, you may need to manually attach the sources jar
*/
task downloadGradleSources() {
doLast {
// Awful hack to find the gradle api location
def gradleApiFile = project.configurations.detachedConfiguration(dependencies.gradleApi()).files.stream()
.filter {
it.name.startsWith("gradle-api")
}.findFirst().orElseThrow()
def gradleApiSources = new File(gradleApiFile.absolutePath.replace(".jar", "-sources.jar"))
def url = "https://services.gradle.org/distributions/gradle-${GradleVersion.current().getVersion()}-src.zip"
gradleApiSources.delete()
println("Downloading (${url}) to (${gradleApiSources})")
gradleApiSources << new URL(url).newInputStream()
}
}
tasks.withType(GenerateModuleMetadata) {
enabled = false
}

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.2-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -26,15 +26,17 @@ package net.fabricmc.loom;
import java.io.File;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import org.cadixdev.lorenz.MappingSet;
import org.cadixdev.mercury.Mercury;
import org.gradle.api.Action;
import org.gradle.api.NamedDomainObjectProvider;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.FileCollection;
import org.gradle.api.tasks.SourceSet;
import net.fabricmc.loom.api.LoomGradleExtensionAPI;
import net.fabricmc.loom.configuration.InstallerData;
@@ -61,7 +63,12 @@ public interface LoomGradleExtension extends LoomGradleExtensionAPI {
LoomFiles getFiles();
NamedDomainObjectProvider<Configuration> createLazyConfiguration(String name);
default NamedDomainObjectProvider<Configuration> createLazyConfiguration(String name) {
return createLazyConfiguration(name, config -> {
});
}
NamedDomainObjectProvider<Configuration> createLazyConfiguration(String name, Action<? super Configuration> configurationAction);
NamedDomainObjectProvider<Configuration> getLazyConfigurationProvider(String name);
@@ -95,16 +102,12 @@ public interface LoomGradleExtension extends LoomGradleExtensionAPI {
return getMappingsProvider().mappedProvider;
}
File getNextMixinMappings();
File getMixinMappings(SourceSet sourceSet);
Set<File> getAllMixinMappings();
FileCollection getAllMixinMappings();
boolean isRootProject();
default boolean ideSync() {
return Boolean.parseBoolean(System.getProperty("idea.sync.active", "false"));
}
default String getIntermediaryUrl(String minecraftVersion) {
return String.format(this.getIntermediaryUrl().get(), minecraftVersion);
}

View File

@@ -43,6 +43,7 @@ import net.fabricmc.loom.configuration.CompileConfiguration;
import net.fabricmc.loom.configuration.FabricApiExtension;
import net.fabricmc.loom.configuration.MavenPublication;
import net.fabricmc.loom.configuration.ide.IdeConfiguration;
import net.fabricmc.loom.configuration.ide.idea.IdeaConfiguration;
import net.fabricmc.loom.decompilers.DecompilerConfiguration;
import net.fabricmc.loom.extension.LoomFiles;
import net.fabricmc.loom.extension.LoomGradleExtensionImpl;
@@ -93,5 +94,6 @@ public class LoomGradlePlugin implements BootstrappedPlugin {
MavenPublication.configure(project);
LoomTasks.registerTasks(project);
DecompilerConfiguration.setup(project);
IdeaConfiguration.setup(project);
}
}

View File

@@ -28,6 +28,7 @@ import java.util.List;
import java.util.function.Consumer;
import org.gradle.api.Action;
import org.gradle.api.DomainObjectCollection;
import org.gradle.api.NamedDomainObjectContainer;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.file.ConfigurableFileCollection;
@@ -63,7 +64,7 @@ public interface LoomGradleExtensionAPI {
getShareRemapCaches().set(true);
}
ListProperty<LoomDecompiler> getGameDecompilers();
DomainObjectCollection<LoomDecompiler> getGameDecompilers();
default void addDecompiler(LoomDecompiler decompiler) {
getGameDecompilers().add(decompiler);
@@ -140,6 +141,15 @@ public interface LoomGradleExtensionAPI {
*/
Property<String> getIntermediaryUrl();
/**
* When true loom will inject interfaces declared in mod manifests into the minecraft jar file.
* This is used to expose interfaces that are implemented on Minecraft classes by mixins at runtime
* in the dev environment.
*
* @return the property controlling interface injection.
*/
Property<Boolean> getEnableInterfaceInjection();
// ===================
// Architectury Loom
// ===================

View File

@@ -26,6 +26,10 @@ package net.fabricmc.loom.api.decompilers;
import java.nio.file.Path;
import org.gradle.api.Project;
import org.gradle.api.file.FileCollection;
import org.jetbrains.annotations.Nullable;
public interface LoomDecompiler {
String name();
@@ -37,4 +41,12 @@ public interface LoomDecompiler {
* @param metaData Additional information that may or may not be needed while decompiling
*/
void decompile(Path compiledJar, Path sourcesDestination, Path linemapDestination, DecompilationMetadata metaData);
/**
* Add additional classpath entries to the decompiler classpath, can return a configuration.
*/
@Nullable
default FileCollection getBootstrapClasspath(Project project) {
return null;
}
}

View File

@@ -27,9 +27,10 @@ package net.fabricmc.loom.api.mappings.layered.spec;
import groovy.lang.Closure;
import groovy.lang.DelegatesTo;
import org.gradle.api.Action;
import org.gradle.util.ConfigureUtil;
import org.jetbrains.annotations.ApiStatus;
import net.fabricmc.loom.util.ClosureAction;
/**
* Used to configure a layered mapping spec.
*/
@@ -52,7 +53,7 @@ public interface LayeredMappingSpecBuilder {
*/
@SuppressWarnings("rawtypes")
default LayeredMappingSpecBuilder officialMojangMappings(@DelegatesTo(value = MojangMappingsSpecBuilder.class, strategy = Closure.DELEGATE_FIRST) Closure closure) {
return officialMojangMappings(mojangMappingsSpecBuilder -> ConfigureUtil.configure(closure, mojangMappingsSpecBuilder));
return officialMojangMappings(new ClosureAction<>(closure));
}
/**
@@ -66,7 +67,7 @@ public interface LayeredMappingSpecBuilder {
@SuppressWarnings("rawtypes")
default LayeredMappingSpecBuilder parchment(Object object, @DelegatesTo(value = ParchmentMappingsSpecBuilder.class, strategy = Closure.DELEGATE_FIRST) Closure closure) {
return parchment(object, parchmentMappingsSpecBuilder -> ConfigureUtil.configure(closure, parchmentMappingsSpecBuilder));
return parchment(object, new ClosureAction<>(closure));
}
LayeredMappingSpecBuilder parchment(Object object, Action<ParchmentMappingsSpecBuilder> action);

View File

@@ -1,179 +0,0 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2020 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.build;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import dev.architectury.tinyremapper.IMappingProvider;
import dev.architectury.tinyremapper.InputTag;
import dev.architectury.tinyremapper.OutputConsumerPath;
import dev.architectury.tinyremapper.TinyRemapper;
import org.gradle.api.Action;
import org.gradle.api.Project;
import org.objectweb.asm.commons.Remapper;
import net.fabricmc.loom.util.CloseableList;
import net.fabricmc.loom.util.LoggerFilter;
import net.fabricmc.stitch.util.Pair;
public class JarRemapper {
private final List<IMappingProvider> mappingProviders = new ArrayList<>();
private final Set<Path> classPath = new HashSet<>();
private final List<RemapData> remapData = new ArrayList<>();
private List<Action<TinyRemapper.Builder>> remapOptions;
public void addMappings(IMappingProvider mappingProvider) {
mappingProviders.add(mappingProvider);
}
public void addToClasspath(Path... paths) {
classPath.addAll(Arrays.asList(paths));
}
public RemapData scheduleRemap(Path input, Path output) {
RemapData data = new RemapData(input, output);
remapData.add(data);
return data;
}
public void remap(Project project) throws IOException {
LoggerFilter.replaceSystemOut();
TinyRemapper.Builder remapperBuilder = TinyRemapper.newRemapper();
remapperBuilder.logger(project.getLogger()::lifecycle);
remapperBuilder.logUnknownInvokeDynamic(false);
mappingProviders.forEach(remapperBuilder::withMappings);
if (remapOptions != null) {
for (Action<TinyRemapper.Builder> remapOption : remapOptions) {
remapOption.execute(remapperBuilder);
}
}
TinyRemapper remapper = remapperBuilder.build();
Path[] remapClasspath = classPath.stream()
.filter(path ->
remapData.stream().noneMatch(remapData -> remapData.input.toString().equals(path.toString()))
)
.toArray(Path[]::new);
remapper.readClassPathAsync(remapClasspath);
for (RemapData data : remapData) {
InputTag tag = remapper.createInputTag();
data.tag = tag;
project.getLogger().info(":remapper input -> " + data.input.getFileName().toString());
try {
remapper.readInputsAsync(tag, data.input);
} catch (Exception e) {
throw new RuntimeException("Failed to read remapper input " + data.input.getFileName().toString(), e);
}
}
//noinspection MismatchedQueryAndUpdateOfCollection
try (CloseableList<OutputConsumerPath> outputConsumers = new CloseableList<>()) {
for (RemapData data : remapData) {
OutputConsumerPath outputConsumer;
project.getLogger().info(":remapper output -> " + data.output.getFileName().toString());
try {
Files.deleteIfExists(data.output);
outputConsumer = new OutputConsumerPath.Builder(data.output).build();
} catch (Exception e) {
throw new RuntimeException("Failed to create remapper output " + data.output.getFileName().toString(), e);
}
outputConsumers.add(outputConsumer);
outputConsumer.addNonClassFiles(data.input);
data.processAccessWidener(remapper.getRemapper());
remapper.apply(outputConsumer, data.tag);
}
remapper.finish();
} catch (Exception e) {
for (RemapData data : remapData) {
// Cleanup bad outputs
Files.deleteIfExists(data.output);
}
throw new IOException("Failed to remap %s files".formatted(remapData.size()), e);
}
remapData.forEach(RemapData::complete);
}
public void addOptions(List<Action<TinyRemapper.Builder>> remapOptions) {
this.remapOptions = remapOptions;
}
public static class RemapData {
public final Path input;
public final Path output;
BiFunction<RemapData, Remapper, Pair<String, byte[]>> accesWidenerSupplier;
BiConsumer<RemapData, Pair<String, byte[]>> onComplete;
private InputTag tag;
private Pair<String, byte[]> accessWidener;
public RemapData(Path input, Path output) {
this.input = input;
this.output = output;
}
public RemapData complete(BiConsumer<RemapData, Pair<String, byte[]>> onComplete) {
this.onComplete = onComplete;
return this;
}
public RemapData supplyAccessWidener(BiFunction<RemapData, Remapper, Pair<String, byte[]>> beforeFinish) {
this.accesWidenerSupplier = beforeFinish;
return this;
}
private void complete() {
if (onComplete != null) {
onComplete.accept(this, accessWidener);
}
}
private void processAccessWidener(Remapper remapper) {
if (accesWidenerSupplier != null) {
accessWidener = accesWidenerSupplier.apply(this, remapper);
}
}
}
}

View File

@@ -27,13 +27,9 @@ package net.fabricmc.loom.build;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
@@ -41,74 +37,35 @@ import java.util.zip.ZipFile;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import org.gradle.api.Project;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.extension.MixinExtension;
import net.fabricmc.loom.util.Pair;
import net.fabricmc.loom.util.ZipUtils;
public final class MixinRefmapHelper {
private MixinRefmapHelper() { }
private static final String FABRIC_MOD_JSON = "fabric.mod.json";
public static boolean addRefmapName(Project project, Path outputPath) {
try {
MixinExtension mixin = LoomGradleExtension.get(project).getMixin();
File output = outputPath.toFile();
Collection<String> allMixinConfigs = LoomGradleExtension.get(project).isForge() ? LoomGradleExtension.get(project).getForge().getMixinConfigs().get() : getMixinConfigurationFiles(readFabricModJson(output));
return mixin.getMixinSourceSetsStream().map(sourceSet -> {
MixinExtension.MixinInformationContainer container = Objects.requireNonNull(
MixinExtension.getMixinInformationContainer(sourceSet)
);
Stream<String> mixinConfigs = sourceSet.getResources()
.matching(container.mixinConfigPattern())
.getFiles()
.stream()
.map(File::getName)
.filter(allMixinConfigs::contains);
String refmapName = container.refmapNameProvider().get();
try {
return ZipUtils.transformJson(JsonObject.class, outputPath, mixinConfigs.map(f -> new Pair<>(f, json -> {
if (!json.has("refmap")) {
json.addProperty("refmap", refmapName);
}
return json;
}))) > 0;
} catch (IOException e) {
throw new UncheckedIOException("Failed to transform mixin configs in jar", e);
}
}).reduce(false, Boolean::logicalOr);
} catch (Exception e) {
project.getLogger().error(e.getMessage());
return false;
}
}
@NotNull
private static JsonObject readFabricModJson(File output) {
@Nullable
public static JsonObject readFabricModJson(File output) {
try (ZipFile zip = new ZipFile(output)) {
ZipEntry entry = zip.getEntry(FABRIC_MOD_JSON);
if (entry == null) {
return null;
}
try (InputStreamReader reader = new InputStreamReader(zip.getInputStream(entry))) {
return LoomGradlePlugin.GSON.fromJson(reader, JsonObject.class);
}
} catch (IOException e) {
throw new RuntimeException("Cannot read file fabric.mod.json in the output jar.", e);
throw new RuntimeException("Cannot read file fabric.mod.json in the jar.", e);
}
}
@NotNull
private static Collection<String> getMixinConfigurationFiles(JsonObject fabricModJson) {
public static Collection<String> getMixinConfigurationFiles(JsonObject fabricModJson) {
JsonArray mixins = fabricModJson.getAsJsonArray("mixins");
if (mixins == null) {

View File

@@ -29,7 +29,6 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import java.util.zip.ZipFile;
import com.google.common.base.Suppliers;
import com.google.common.io.Files;
@@ -59,6 +58,7 @@ import net.fabricmc.loom.configuration.processors.dependency.ModDependencyInfo;
import net.fabricmc.loom.configuration.processors.dependency.RemapData;
import net.fabricmc.loom.util.Checksum;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.ModUtils;
import net.fabricmc.loom.util.OperatingSystem;
import net.fabricmc.loom.util.SourceRemapper;
@@ -94,7 +94,7 @@ public class ModCompileRemapper {
String name = extension.isForgeAndOfficial() ? "B" + checksum.get() : artifact.getModuleVersion().getId().getName();
String version = extension.isForgeAndOfficial() ? "B" + checksum.get() : replaceIfNullOrEmpty(artifact.getModuleVersion().getId().getVersion(), () -> Checksum.truncatedSha256(artifact.getFile()));
if (!shouldRemapMod(logger, artifact.getFile(), artifact.getId(), extension.isForge(), sourceConfig.getName())) {
if (!ModUtils.shouldRemapMod(artifact.getFile(), artifact.getId(), extension.isForge(), sourceConfig.getName())) {
addToRegularCompile(project, regularConfig, artifact);
continue;
}
@@ -121,7 +121,7 @@ public class ModCompileRemapper {
// Create a mod dependency for each file in the file collection
for (File artifact : files) {
if (!shouldRemapMod(logger, artifact, artifact.getName(), extension.isForge(), sourceConfig.getName())) {
if (!ModUtils.shouldRemapMod(artifact, artifact.getName(), extension.isForge(), sourceConfig.getName())) {
dependencies.add(regularConfig.getName(), project.files(artifact));
continue;
}
@@ -165,37 +165,6 @@ public class ModCompileRemapper {
}
}
/**
* Checks if an artifact is a fabric mod, according to the presence of a fabric.mod.json.
*/
private static boolean shouldRemapMod(Logger logger, File artifact, Object id, boolean forge, String config) {
try (ZipFile zipFile = new ZipFile(artifact)) {
if (zipFile.getEntry("architectury.common.marker") != null) {
logger.info("Found architectury common mod in " + config + ": {}", id);
return true;
}
if (forge) {
if (zipFile.getEntry("META-INF/mods.toml") != null) {
logger.info("Found Forge mod in " + config + ": {}", id);
return true;
}
logger.lifecycle(":could not find forge mod in " + config + " but forcing: {}", id);
return true;
} else {
if (zipFile.getEntry("fabric.mod.json") != null) {
logger.info("Found Fabric mod in " + config + ": {}", id);
return true;
}
}
return false;
} catch (IOException e) {
return false;
}
}
private static void addToRegularCompile(Project project, Configuration regularCompile, ResolvedArtifact artifact) {
project.getLogger().info(":providing " + artifact);
DependencyHandler dependencies = project.getDependencies();

View File

@@ -40,8 +40,9 @@ import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.tasks.SourceSet;
import net.fabricmc.loom.extension.MixinExtension;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.ide.idea.IdeaUtils;
import net.fabricmc.loom.extension.MixinExtension;
import net.fabricmc.loom.util.Constants;
/**
@@ -85,7 +86,7 @@ public abstract class AnnotationProcessorInvoker<T extends Task> {
Path mappings = loom.isForge() ? loom.getMappingsProvider().mixinTinyMappingsWithSrg : loom.getMappingsProvider().tinyMappings;
Map<String, String> args = new HashMap<>() {{
put(Constants.MixinArguments.IN_MAP_FILE_NAMED_INTERMEDIARY, mappings.toFile().getCanonicalPath());
put(Constants.MixinArguments.OUT_MAP_FILE_NAMED_INTERMEDIARY, loom.getNextMixinMappings().getCanonicalPath());
put(Constants.MixinArguments.OUT_MAP_FILE_NAMED_INTERMEDIARY, loom.getMixinMappings(sourceSet).getCanonicalPath());
put(Constants.MixinArguments.OUT_REFMAP_FILE, getRefmapDestination(task, refmapName));
put(Constants.MixinArguments.DEFAULT_OBFUSCATION_ENV, "named:intermediary");
}};
@@ -99,9 +100,8 @@ public abstract class AnnotationProcessorInvoker<T extends Task> {
public void configureMixin() {
ConfigurationContainer configs = project.getConfigurations();
LoomGradleExtension extension = LoomGradleExtension.get(project);
if (!extension.ideSync()) {
if (!IdeaUtils.isIdeaSync()) {
for (Configuration processorConfig : apConfigurations) {
project.getLogger().info("Adding mixin to classpath of AP config: " + processorConfig.getName());
// Pass named MC classpath to mixin AP classpath

View File

@@ -58,7 +58,7 @@ public class JavaApInvoker extends AnnotationProcessorInvoker<JavaCompile> {
@Override
protected File getRefmapDestinationDir(JavaCompile task) {
return task.getDestinationDir();
return task.getDestinationDirectory().getAsFile().get();
}
private static String getAptConfigurationName(String sourceSet) {

View File

@@ -84,7 +84,7 @@ public class KaptApInvoker extends AnnotationProcessorInvoker<JavaCompile> {
try {
String refmapName = Objects.requireNonNull(MixinExtension.getMixinInformationContainer(sourceSet)).refmapNameProvider().get();
Path src = Paths.get(getRefmapDestination(task, refmapName));
Path dest = Paths.get(task.getDestinationDir().toString(), refmapName);
Path dest = Paths.get(task.getDestinationDirectory().get().getAsFile().toString(), refmapName);
// Possible that no mixin annotations exist
if (Files.exists(src)) {

View File

@@ -59,6 +59,6 @@ public class ScalaApInvoker extends AnnotationProcessorInvoker<ScalaCompile> {
@Override
protected File getRefmapDestinationDir(ScalaCompile task) {
return task.getDestinationDir();
return task.getDestinationDirectory().get().getAsFile();
}
}

View File

@@ -0,0 +1,212 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2021 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.build.nesting;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Locale;
import java.util.Set;
import com.google.common.collect.Sets;
import com.google.common.hash.Hashing;
import com.google.gson.JsonObject;
import org.apache.commons.io.FileUtils;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.ProjectDependency;
import org.gradle.api.artifacts.ResolvedArtifact;
import org.gradle.api.artifacts.ResolvedConfiguration;
import org.gradle.api.artifacts.ResolvedDependency;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.bundling.AbstractArchiveTask;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.task.RemapTaskConfiguration;
import net.fabricmc.loom.util.ModUtils;
import net.fabricmc.loom.util.ZipUtils;
public final class IncludedJarFactory {
private final Project project;
public IncludedJarFactory(Project project) {
this.project = project;
}
public Provider<ConfigurableFileCollection> getNestedJars(final Configuration configuration) {
return project.provider(() -> {
final ConfigurableFileCollection files = project.files();
final Set<String> visited = Sets.newHashSet();
files.from(getProjectDeps(configuration, visited));
files.from(getFileDeps(configuration, visited));
files.builtBy(configuration.getBuildDependencies());
return files;
});
}
private ConfigurableFileCollection getFileDeps(Configuration configuration, Set<String> visited) {
final ConfigurableFileCollection files = project.files();
final ResolvedConfiguration resolvedConfiguration = configuration.getResolvedConfiguration();
final Set<ResolvedDependency> dependencies = resolvedConfiguration.getFirstLevelModuleDependencies();
for (ResolvedDependency dependency : dependencies) {
if (!visited.add(dependency.getModuleGroup() + ":" + dependency.getModuleName() + ":" + dependency.getModuleVersion())) {
continue;
}
for (ResolvedArtifact artifact : dependency.getModuleArtifacts()) {
Metadata metadata = new Metadata(
dependency.getModuleGroup(),
dependency.getModuleName(),
dependency.getModuleVersion(),
artifact.getClassifier()
);
files.from(project.provider(() -> getNestableJar(artifact.getFile(), metadata)));
}
}
return files;
}
private ConfigurableFileCollection getProjectDeps(Configuration configuration, Set<String> visited) {
final ConfigurableFileCollection files = project.files();
for (Dependency dependency : configuration.getDependencies()) {
if (dependency instanceof ProjectDependency projectDependency) {
if (!visited.add(dependency.getGroup() + ":" + dependency.getName() + ":" + dependency.getVersion())) {
continue;
}
// Get the outputs of the project
final Project dependentProject = projectDependency.getDependencyProject();
Collection<Task> remapJarTasks = dependentProject.getTasksByName(RemapTaskConfiguration.REMAP_JAR_TASK_NAME, false);
Collection<Task> jarTasks = dependentProject.getTasksByName(JavaPlugin.JAR_TASK_NAME, false);
if (remapJarTasks.isEmpty() && jarTasks.isEmpty()) {
throw new UnsupportedOperationException("%s does not have a remapJar or jar task, cannot nest it".formatted(dependentProject.getName()));
}
for (Task task : remapJarTasks.isEmpty() ? jarTasks : remapJarTasks) {
if (task instanceof AbstractArchiveTask archiveTask) {
final Metadata metadata = new Metadata(
projectDependency.getGroup(),
projectDependency.getName(),
projectDependency.getVersion(),
archiveTask.getArchiveClassifier().getOrNull()
);
Provider<File> provider = archiveTask.getArchiveFile().map(regularFile -> getNestableJar(regularFile.getAsFile(), metadata));
files.from(provider);
files.builtBy(task);
} else {
throw new UnsupportedOperationException("Cannot nest none AbstractArchiveTask task: " + task.getName());
}
}
}
}
return files;
}
private File getNestableJar(final File input, final Metadata metadata) {
if (ModUtils.isMod(input)) {
// Input is a mod, nothing needs to be done.
return input;
}
LoomGradleExtension extension = LoomGradleExtension.get(project);
File tempDir = new File(extension.getFiles().getUserCache(), "temp/modprocessing");
if (!tempDir.exists()) {
tempDir.mkdirs();
}
File tempFile = new File(tempDir, input.getName());
if (tempFile.exists()) {
tempFile.delete();
}
try {
FileUtils.copyFile(input, tempFile);
ZipUtils.add(tempFile.toPath(), "fabric.mod.json", generateModForDependency(metadata).getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
throw new UncheckedIOException("Failed to add dummy mod while including %s".formatted(input), e);
}
return tempFile;
}
// Generates a barebones mod for a dependency
private static String generateModForDependency(Metadata metadata) {
String modId = (metadata.group() + "_" + metadata.name() + metadata.classifier())
.replaceAll("\\.", "_")
.toLowerCase(Locale.ENGLISH);
// Fabric Loader can't handle modIds longer than 64 characters
if (modId.length() > 64) {
String hash = Hashing.sha256()
.hashString(modId, StandardCharsets.UTF_8)
.toString();
modId = modId.substring(0, 50) + hash.substring(0, 14);
}
final JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("schemaVersion", 1);
jsonObject.addProperty("id", modId);
jsonObject.addProperty("version", metadata.version());
jsonObject.addProperty("name", metadata.name());
JsonObject custom = new JsonObject();
custom.addProperty("fabric-loom:generated", true);
jsonObject.add("custom", custom);
return LoomGradlePlugin.GSON.toJson(jsonObject);
}
private record Metadata(String group, String name, String version, @Nullable String classifier) {
@Override
public String classifier() {
if (classifier == null) {
return "";
} else {
return "_" + classifier;
}
}
}
}

View File

@@ -36,7 +36,7 @@ import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.gradle.api.UncheckedIOException;
import org.gradle.api.logging.Logger;
import org.slf4j.Logger;
import net.fabricmc.loom.util.ModUtils;
import net.fabricmc.loom.util.Pair;
@@ -69,6 +69,7 @@ public class JarNester {
for (File file : jars) {
String nestedJarPath = "META-INF/jars/" + file.getName();
Preconditions.checkArgument(ModUtils.isMod(file), "Cannot nest none mod jar: " + file.getName());
for (JsonElement nestedJar : nestedJars) {
JsonObject jsonObject = nestedJar.getAsJsonObject();

View File

@@ -1,281 +0,0 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2019-2021 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.build.nesting;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import javax.annotation.Nullable;
import com.google.gson.JsonObject;
import org.apache.commons.io.FileUtils;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.DependencySet;
import org.gradle.api.artifacts.ProjectDependency;
import org.gradle.api.artifacts.ResolvedArtifact;
import org.gradle.api.artifacts.ResolvedConfiguration;
import org.gradle.api.artifacts.ResolvedDependency;
import org.gradle.api.tasks.bundling.AbstractArchiveTask;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.task.RemapJarTask;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.ZipUtils;
public final class NestedDependencyProvider implements NestedJarProvider {
final Project project;
final List<DependencyInfo<?>> files;
private NestedDependencyProvider(Project project, List<DependencyInfo<?>> files) {
this.project = project;
this.files = files;
}
public static NestedDependencyProvider createNestedDependencyProviderFromConfiguration(Project project, Configuration configuration) {
List<DependencyInfo<?>> fileList = new ArrayList<>();
Set<String> visited = new HashSet<>();
fileList.addAll(populateProjectDependencies(configuration, visited));
fileList.addAll(populateResolvedDependencies(configuration, visited));
return new NestedDependencyProvider(project, fileList);
}
// Looks for any deps that require a sub project to be built first
public static List<RemapJarTask> getRequiredTasks(Project project) {
List<RemapJarTask> remapTasks = new ArrayList<>();
Configuration configuration = project.getConfigurations().getByName(Constants.Configurations.INCLUDE);
DependencySet dependencies = configuration.getDependencies();
for (Dependency dependency : dependencies) {
if (dependency instanceof ProjectDependency projectDependency) {
Project dependencyProject = projectDependency.getDependencyProject();
for (Task task : dependencyProject.getTasksByName("remapJar", false)) {
if (task instanceof RemapJarTask remapJarTask) {
remapTasks.add(remapJarTask);
}
}
}
}
return remapTasks;
}
private static List<DependencyInfo<ProjectDependency>> populateProjectDependencies(Configuration configuration, Set<String> visited) {
List<DependencyInfo<ProjectDependency>> fileList = new ArrayList<>();
for (Dependency dependency : configuration.getDependencies()) {
if (dependency instanceof ProjectDependency projectDependency) {
Project dependencyProject = projectDependency.getDependencyProject();
visited.add(dependency.getGroup() + ":" + dependency.getName() + ":" + dependency.getVersion());
Collection<Task> remapJarTasks = dependencyProject.getTasksByName("remapJar", false);
Collection<Task> jarTasks = dependencyProject.getTasksByName("jar", false);
for (Task task : remapJarTasks.isEmpty() ? jarTasks : remapJarTasks) {
if (task instanceof AbstractArchiveTask abstractArchiveTask) {
fileList.add(new DependencyInfo<>(
projectDependency,
new ProjectDependencyMetaExtractor(),
abstractArchiveTask.getArchiveFile().get().getAsFile(),
abstractArchiveTask.getArchiveClassifier().getOrNull()
));
}
}
}
}
return fileList;
}
private static List<DependencyInfo<ResolvedDependency>> populateResolvedDependencies(Configuration configuration, Set<String> visited) {
ResolvedConfiguration resolvedConfiguration = configuration.getResolvedConfiguration();
Set<ResolvedDependency> dependencies = resolvedConfiguration.getFirstLevelModuleDependencies();
List<DependencyInfo<ResolvedDependency>> fileList = new ArrayList<>();
for (ResolvedDependency dependency : dependencies) {
if (visited.contains(dependency.getModuleGroup() + ":" + dependency.getModuleName() + ":" + dependency.getModuleVersion())) {
continue;
}
for (ResolvedArtifact artifact : dependency.getModuleArtifacts()) {
fileList.add(new DependencyInfo<>(
dependency,
new ResolvedDependencyMetaExtractor(),
artifact.getFile(),
artifact.getClassifier()
));
}
}
return fileList;
}
@Override
public List<File> provide() {
List<File> fileList = new ArrayList<>();
for (DependencyInfo<?> metaFile : files) {
metaFile.validateInputs();
File file = metaFile.file;
//A lib that doesnt have a mod.json, we turn it into a fake mod
if (!ZipUtils.contains(file.toPath(), "fabric.mod.json")) {
LoomGradleExtension extension = LoomGradleExtension.get(project);
File tempDir = new File(extension.getFiles().getUserCache(), "temp/modprocessing");
if (!tempDir.exists()) {
tempDir.mkdirs();
}
File tempFile = new File(tempDir, file.getName());
if (tempFile.exists()) {
tempFile.delete();
}
try {
FileUtils.copyFile(file, tempFile);
} catch (IOException e) {
throw new RuntimeException("Failed to copy file", e);
}
try {
ZipUtils.add(tempFile.toPath(), "fabric.mod.json", generateModForDependency(metaFile).getBytes());
} catch (IOException e) {
throw new UncheckedIOException("Failed to add dummy mod while including %s".formatted(file), e);
}
fileList.add(tempFile);
} else {
// Default copy the jar right in
fileList.add(file);
}
}
return fileList;
}
// Generates a barebones mod for a dependency
private static <D> String generateModForDependency(DependencyInfo<D> info) {
DependencyMetaExtractor<D> metaExtractor = info.metaExtractor;
D dependency = info.dependency;
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("schemaVersion", 1);
jsonObject.addProperty("id",
(metaExtractor.group(dependency) + "_" + metaExtractor.name(dependency) + info.getClassifierSuffix())
.replaceAll("\\.", "_")
.toLowerCase(Locale.ENGLISH)
);
jsonObject.addProperty("version", metaExtractor.version(dependency));
jsonObject.addProperty("name", metaExtractor.name(dependency));
JsonObject custom = new JsonObject();
custom.addProperty("fabric-loom:generated", true);
jsonObject.add("custom", custom);
return LoomGradlePlugin.GSON.toJson(jsonObject);
}
private record DependencyInfo<D>(D dependency, DependencyMetaExtractor<D> metaExtractor, File file, @Nullable String classifier) {
void validateInputs() {
if (!file.exists()) {
throw new RuntimeException("Failed to include nested jars, as it could not be found @ " + file.getAbsolutePath());
}
if (file.isDirectory() || !file.getName().endsWith(".jar")) {
throw new RuntimeException("Failed to include nested jars, as file was not a jar: " + file.getAbsolutePath());
}
}
String getClassifierSuffix() {
if (classifier == null) {
return "";
} else {
return "_" + classifier;
}
}
}
private interface DependencyMetaExtractor<D> {
String group(D dependency);
String version(D dependency);
String name(D dependency);
}
private static final class ProjectDependencyMetaExtractor implements DependencyMetaExtractor<ProjectDependency> {
@Override
public String group(ProjectDependency dependency) {
return dependency.getGroup();
}
@Override
public String version(ProjectDependency dependency) {
return dependency.getVersion();
}
@Override
public String name(ProjectDependency dependency) {
return dependency.getName();
}
}
private static final class ResolvedDependencyMetaExtractor implements DependencyMetaExtractor<ResolvedDependency> {
@Override
public String group(ResolvedDependency dependency) {
return dependency.getModuleGroup();
}
@Override
public String version(ResolvedDependency dependency) {
return dependency.getModuleVersion();
}
@Override
public String name(ResolvedDependency dependency) {
return dependency.getModuleName();
}
}
}

View File

@@ -1,69 +0,0 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2016-2021 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.build.nesting;
import java.io.File;
import java.util.Collection;
import java.util.Set;
import com.google.common.base.Preconditions;
import org.gradle.api.Project;
import net.fabricmc.loom.util.ModUtils;
public final class NestedJarPathProvider implements NestedJarProvider {
private final Set<Object> nestedPaths;
private Set<File> files = null;
public NestedJarPathProvider(Set<Object> nestedPaths) {
this.nestedPaths = nestedPaths;
}
private Set<File> resolve(Project project) {
return project.files(nestedPaths).getFiles();
}
@Override
public void prepare(Project project) {
if (files == null) {
files = resolve(project);
}
}
@Override
public Collection<File> provide() {
validateFiles();
return files;
}
private void validateFiles() {
Preconditions.checkNotNull(files, "null files to nest, was prepare called?");
for (File file : files) {
Preconditions.checkArgument(file.getName().endsWith(".jar"), String.format("Tried to nest %s but it is not a jar", file.getAbsolutePath()));
Preconditions.checkArgument(file.exists(), String.format("Tried to nest jar %s but it does not exist", file.getAbsolutePath()));
Preconditions.checkArgument(ModUtils.isMod(file), String.format("Cannot use nest none mod jar %s", file.getAbsolutePath()));
}
}
}

View File

@@ -24,22 +24,24 @@
package net.fabricmc.loom.configuration;
import java.io.File;
import java.nio.charset.StandardCharsets;
import org.gradle.api.NamedDomainObjectProvider;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginConvention;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.tasks.AbstractCopyTask;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.TaskContainer;
import org.gradle.api.tasks.compile.JavaCompile;
import org.gradle.api.tasks.javadoc.Javadoc;
import org.gradle.jvm.tasks.Jar;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.build.mixin.JavaApInvoker;
import net.fabricmc.loom.build.mixin.KaptApInvoker;
import net.fabricmc.loom.build.mixin.ScalaApInvoker;
import net.fabricmc.loom.configuration.ide.SetupIntelijRunConfigs;
import net.fabricmc.loom.configuration.providers.LaunchProvider;
import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl;
import net.fabricmc.loom.configuration.providers.forge.FieldMigratedMappingsProvider;
@@ -51,7 +53,9 @@ import net.fabricmc.loom.configuration.providers.forge.PatchProvider;
import net.fabricmc.loom.configuration.providers.forge.SrgProvider;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.extension.MixinExtension;
import net.fabricmc.loom.task.GenerateSourcesTask;
import net.fabricmc.loom.task.GenVsCodeProjectTask;
import net.fabricmc.loom.task.UnpickJarTask;
import net.fabricmc.loom.util.Constants;
public final class CompileConfiguration {
@@ -73,12 +77,17 @@ public final class CompileConfiguration {
}
});
extension.createLazyConfiguration(Constants.Configurations.MOD_COMPILE_CLASSPATH).configure(configuration -> configuration.setTransitive(true));
extension.createLazyConfiguration(Constants.Configurations.MOD_COMPILE_CLASSPATH_MAPPED).configure(configuration -> configuration.setTransitive(false));
extension.createLazyConfiguration(Constants.Configurations.MINECRAFT_NAMED).configure(configuration -> configuration.setTransitive(false)); // The launchers do not recurse dependencies
extension.createLazyConfiguration(Constants.Configurations.MINECRAFT_DEPENDENCIES).configure(configuration -> configuration.setTransitive(false));
extension.createLazyConfiguration(Constants.Configurations.LOADER_DEPENDENCIES).configure(configuration -> configuration.setTransitive(false));
extension.createLazyConfiguration(Constants.Configurations.MINECRAFT).configure(configuration -> configuration.setTransitive(false));
extension.createLazyConfiguration(Constants.Configurations.MOD_COMPILE_CLASSPATH, configuration -> configuration.setTransitive(true));
extension.createLazyConfiguration(Constants.Configurations.MOD_COMPILE_CLASSPATH_MAPPED, configuration -> configuration.setTransitive(false));
extension.createLazyConfiguration(Constants.Configurations.MINECRAFT_NAMED, configuration -> configuration.setTransitive(false)); // The launchers do not recurse dependencies
NamedDomainObjectProvider<Configuration> serverDeps = extension.createLazyConfiguration(Constants.Configurations.MINECRAFT_SERVER_DEPENDENCIES, configuration -> configuration.setTransitive(false));
extension.createLazyConfiguration(Constants.Configurations.MINECRAFT_DEPENDENCIES, configuration -> {
configuration.extendsFrom(serverDeps.get());
configuration.setTransitive(false);
});
extension.createLazyConfiguration(Constants.Configurations.MINECRAFT_NATIVES, configuration -> configuration.setTransitive(false));
extension.createLazyConfiguration(Constants.Configurations.LOADER_DEPENDENCIES, configuration -> configuration.setTransitive(false));
extension.createLazyConfiguration(Constants.Configurations.MINECRAFT, configuration -> configuration.setTransitive(false));
if (extension.isForge()) {
extension.createLazyConfiguration(Constants.Configurations.FORGE).configure(configuration -> configuration.setTransitive(false));
@@ -113,11 +122,10 @@ public final class CompileConfiguration {
}
if (extension.supportsInclude()) {
extension.createLazyConfiguration(Constants.Configurations.INCLUDE).configure(configuration -> configuration.setTransitive(false)); // Dont get transitive deps
extension.createLazyConfiguration(Constants.Configurations.INCLUDE, configuration -> configuration.setTransitive(false)); // Dont get transitive deps
}
extension.createLazyConfiguration(Constants.Configurations.MAPPING_CONSTANTS);
extension.createLazyConfiguration(Constants.Configurations.NAMED_ELEMENTS).configure(configuration -> {
extension.createLazyConfiguration(Constants.Configurations.NAMED_ELEMENTS, configuration -> {
configuration.setCanBeConsumed(true);
configuration.setCanBeResolved(false);
configuration.extendsFrom(project.getConfigurations().getByName(JavaPlugin.API_CONFIGURATION_NAME));
@@ -173,16 +181,15 @@ public final class CompileConfiguration {
}
public static void configureCompile(Project p) {
JavaPluginConvention javaModule = (JavaPluginConvention) p.getConvention().getPlugins().get("java");
final JavaPluginExtension javaPluginExtension = p.getExtensions().getByType(JavaPluginExtension.class);
LoomGradleExtension extension = LoomGradleExtension.get(p);
SourceSet main = javaModule.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME);
Javadoc javadoc = (Javadoc) p.getTasks().getByName(JavaPlugin.JAVADOC_TASK_NAME);
javadoc.setClasspath(main.getOutput().plus(main.getCompileClasspath()));
p.getTasks().named(JavaPlugin.JAVADOC_TASK_NAME, Javadoc.class).configure(javadoc -> {
final SourceSet main = javaPluginExtension.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME);
javadoc.setClasspath(main.getOutput().plus(main.getCompileClasspath()));
});
p.afterEvaluate(project -> {
LoomGradleExtension extension = LoomGradleExtension.get(project);
LoomDependencyManager dependencyManager = new LoomDependencyManager();
extension.setDependencyManager(dependencyManager);
@@ -208,31 +215,23 @@ public final class CompileConfiguration {
dependencyManager.handleDependencies(project);
project.getTasks().getByName("idea").finalizedBy(project.getTasks().getByName("genIdeaWorkspace"));
project.getTasks().getByName("eclipse").finalizedBy(project.getTasks().getByName("genEclipseRuns"));
project.getTasks().getByName("cleanEclipse").finalizedBy(project.getTasks().getByName("cleanEclipseRuns"));
SetupIntelijRunConfigs.setup(project);
GenVsCodeProjectTask.generate(project);
extension.getRemapArchives().finalizeValue();
// Enables the default mod remapper
if (extension.getRemapArchives().get()) {
RemapConfiguration.setupDefaultRemap(project);
} else {
Jar jarTask = (Jar) project.getTasks().getByName("jar");
extension.getUnmappedModCollection().from(jarTask);
}
MixinExtension mixin = LoomGradleExtension.get(project).getMixin();
if (mixin.getUseLegacyMixinAp().get()) {
setupMixinAp(project, mixin);
}
configureDecompileTasks(project);
});
finalizedBy(p, "idea", "genIdeaWorkspace");
finalizedBy(p, "eclipse", "genEclipseRuns");
finalizedBy(p, "cleanEclipse", "cleanEclipseRuns");
// Add the "dev" jar to the "namedElements" configuration
p.artifacts(artifactHandler -> artifactHandler.add(Constants.Configurations.NAMED_ELEMENTS, p.getTasks().getByName("jar")));
p.artifacts(artifactHandler -> artifactHandler.add(Constants.Configurations.NAMED_ELEMENTS, p.getTasks().named("jar")));
// Ensure that the encoding is set to UTF-8, no matter what the system default is
// this fixes some edge cases with special characters not displaying correctly
@@ -269,7 +268,47 @@ public final class CompileConfiguration {
}
}
private static void configureDecompileTasks(Project project) {
final TaskContainer tasks = project.getTasks();
final LoomGradleExtension extension = LoomGradleExtension.get(project);
MappingsProviderImpl mappingsProvider = extension.getMappingsProvider();
File mappedJar = mappingsProvider.mappedProvider.getMappedJar();
if (mappingsProvider.hasUnpickDefinitions()) {
File outputJar = mappingsProvider.mappedProvider.getUnpickedJar();
tasks.register("unpickJar", UnpickJarTask.class, unpickJarTask -> {
unpickJarTask.getUnpickDefinitions().set(mappingsProvider.getUnpickDefinitionsFile());
unpickJarTask.getInputJar().set(mappingsProvider.mappedProvider.getMappedJar());
unpickJarTask.getOutputJar().set(outputJar);
});
mappedJar = outputJar;
}
final File inputJar = mappedJar;
extension.getGameDecompilers().configureEach(decompiler -> {
String taskName = "genSourcesWith" + decompiler.name();
// Set the input jar for the task after evaluation has occurred.
tasks.named(taskName, GenerateSourcesTask.class).configure(task -> {
task.getInputJar().set(inputJar);
if (mappingsProvider.hasUnpickDefinitions()) {
task.dependsOn(tasks.named("unpickJar"));
}
});
});
}
private static void extendsFrom(String a, String b, Project project) {
project.getConfigurations().getByName(a, configuration -> configuration.extendsFrom(project.getConfigurations().getByName(b)));
}
private static void finalizedBy(Project project, String a, String b) {
project.getTasks().named(a).configure(task -> task.finalizedBy(project.getTasks().named(b)));
}
}

View File

@@ -1,92 +0,0 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2021 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.configuration;
import java.util.Optional;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import dev.architectury.tinyremapper.TinyRemapper;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Dependency;
import org.gradle.util.GradleVersion;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.util.Constants;
public final record JarManifestConfiguration(Project project) {
public void configure(Manifest manifest) {
// Dont set when running the reproducible build tests as it will break them when anything updates
if (Boolean.getBoolean("loom.test.reproducible")) {
return;
}
LoomGradleExtension extension = LoomGradleExtension.get(project);
Attributes attributes = manifest.getMainAttributes();
Optional<String> tinyRemapperVersion = Optional.ofNullable(TinyRemapper.class.getPackage().getImplementationVersion());
attributes.putValue("Fabric-Gradle-Version", GradleVersion.current().getVersion());
attributes.putValue("Fabric-Loom-Version", LoomGradlePlugin.LOOM_VERSION);
attributes.putValue("Fabric-Mixin-Compile-Extensions-Version", Constants.Dependencies.Versions.MIXIN_COMPILE_EXTENSIONS);
attributes.putValue("Fabric-Minecraft-Version", extension.getMinecraftProvider().minecraftVersion());
tinyRemapperVersion.ifPresent(s -> attributes.putValue("Fabric-Tiny-Remapper-Version", s));
getLoaderVersion().ifPresent(s -> attributes.putValue("Fabric-Loader-Version", s));
// This can be overridden by mods if required
if (!attributes.containsKey("Fabric-Mixin-Version")) {
addMixinVersion(attributes);
}
}
private void addMixinVersion(Attributes attributes) {
// Not super ideal that this uses the mod compile classpath, should prob look into making this not a thing at somepoint
Optional<Dependency> dependency = project.getConfigurations().getByName(Constants.Configurations.LOADER_DEPENDENCIES)
.getDependencies()
.stream()
.filter(dep -> "sponge-mixin".equals(dep.getName()))
.findFirst();
if (dependency.isEmpty()) {
project.getLogger().warn("Could not determine Mixin version for jar manifest");
return;
}
attributes.putValue("Fabric-Mixin-Version", dependency.get().getVersion());
attributes.putValue("Fabric-Mixin-Group", dependency.get().getGroup());
}
private Optional<String> getLoaderVersion() {
LoomGradleExtension extension = LoomGradleExtension.get(project);
if (extension.getInstallerData() == null) {
project.getLogger().warn("Could not determine fabric loader version for jar manifest");
return Optional.empty();
}
return Optional.of(extension.getInstallerData().version());
}
}

View File

@@ -46,6 +46,7 @@ import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.LoomRepositoryPlugin;
import net.fabricmc.loom.build.ModCompileRemapper;
import net.fabricmc.loom.configuration.DependencyProvider.DependencyInfo;
import net.fabricmc.loom.configuration.ide.idea.IdeaUtils;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.SourceRemapper;
@@ -210,7 +211,7 @@ public class LoomDependencyManager {
loaderDepsConfig.getDependencies().add(modDep);
// TODO: work around until https://github.com/FabricMC/Mixin/pull/60 and https://github.com/FabricMC/fabric-mixin-compile-extensions/issues/14 is fixed.
if (!extension.ideSync() && extension.getMixin().getUseLegacyMixinAp().get()) {
if (!IdeaUtils.isIdeaSync() && extension.getMixin().getUseLegacyMixinAp().get()) {
apDepsConfig.getDependencies().add(modDep);
}

View File

@@ -1,249 +0,0 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2021 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.configuration;
import java.io.IOException;
import java.util.Set;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import org.gradle.api.Action;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.UnknownTaskException;
import org.gradle.api.artifacts.ConfigurablePublishArtifact;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.PublishArtifact;
import org.gradle.api.artifacts.dsl.ArtifactHandler;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.tasks.bundling.AbstractArchiveTask;
import org.gradle.api.tasks.bundling.Jar;
import org.jetbrains.annotations.ApiStatus;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.build.JarRemapper;
import net.fabricmc.loom.build.nesting.NestedDependencyProvider;
import net.fabricmc.loom.task.AbstractLoomTask;
import net.fabricmc.loom.task.RemapAllSourcesTask;
import net.fabricmc.loom.task.RemapJarTask;
import net.fabricmc.loom.task.RemapSourcesJarTask;
import net.fabricmc.loom.util.PropertyUtil;
import net.fabricmc.loom.util.SourceRemapper;
import net.fabricmc.loom.util.aw2at.Aw2At;
public class RemapConfiguration {
private static final String DEFAULT_JAR_TASK_NAME = JavaPlugin.JAR_TASK_NAME;
private static final String DEFAULT_SOURCES_JAR_TASK_NAME = "sourcesJar";
private static final String DEFAULT_REMAP_JAR_TASK_NAME = "remapJar";
private static final String DEFAULT_REMAP_SOURCES_JAR_TASK_NAME = "remapSourcesJar";
private static final String DEFAULT_REMAP_ALL_JARS_TASK_NAME = "remapAllJars";
private static final String DEFAULT_REMAP_ALL_SOURCES_TASK_NAME = "remapAllSources";
public static void setupDefaultRemap(Project project) {
setupRemap(project, true, DEFAULT_JAR_TASK_NAME, DEFAULT_SOURCES_JAR_TASK_NAME, DEFAULT_REMAP_JAR_TASK_NAME, DEFAULT_REMAP_SOURCES_JAR_TASK_NAME, DEFAULT_REMAP_ALL_JARS_TASK_NAME, DEFAULT_REMAP_ALL_SOURCES_TASK_NAME);
LoomGradleExtension extension = LoomGradleExtension.get(project);
extension.getSetupRemappedVariants().finalizeValue();
if (extension.getSetupRemappedVariants().get()) {
ArtifactHandler artifacts = project.getArtifacts();
project.getTasks().named(DEFAULT_REMAP_JAR_TASK_NAME, task -> {
artifacts.add(JavaPlugin.API_ELEMENTS_CONFIGURATION_NAME, task, artifactConfigurationAction(task, DEFAULT_REMAP_JAR_TASK_NAME, project));
artifacts.add(JavaPlugin.RUNTIME_ELEMENTS_CONFIGURATION_NAME, task, artifactConfigurationAction(task, DEFAULT_REMAP_JAR_TASK_NAME, project));
});
project.getTasks().named(DEFAULT_REMAP_SOURCES_JAR_TASK_NAME, RemapSourcesJarTask.class, task -> {
if (!project.getConfigurations().getNames().contains(JavaPlugin.SOURCES_ELEMENTS_CONFIGURATION_NAME)) {
// Sources jar may not have been created with withSourcesJar
project.getLogger().info("Not publishing sources jar as it was not found. Use java.withSourcesJar() to fix.");
return;
}
PublishArtifact artifact = artifacts.add(JavaPlugin.SOURCES_ELEMENTS_CONFIGURATION_NAME, task.getOutput(), artifactConfigurationAction(task, DEFAULT_REMAP_ALL_SOURCES_TASK_NAME, project));
// Remove the existing artifact that does not run remapSourcesJar.
// It doesn't seem to hurt, but I'm not sure if the file-level duplicates cause issues.
Configuration configuration = project.getConfigurations().getByName(JavaPlugin.SOURCES_ELEMENTS_CONFIGURATION_NAME);
configuration.getArtifacts().removeIf(a -> a != artifact && artifact.getFile().equals(a.getFile()));
// Architectury Loom Patch
// Since we make sourcesJar and remapSourcesJar output files separate, we have to remove the dev sources jar file here
configuration.getArtifacts().removeIf(a -> a != artifact && task.getInput().get().getAsFile().equals(a.getFile()));
});
// Remove -dev jars from the default jar task
for (String configurationName : new String[] { JavaPlugin.API_ELEMENTS_CONFIGURATION_NAME, JavaPlugin.RUNTIME_ELEMENTS_CONFIGURATION_NAME }) {
Configuration configuration = project.getConfigurations().getByName(configurationName);
configuration.getArtifacts().removeIf(artifact -> {
Task jarTask = project.getTasks().getByName(DEFAULT_JAR_TASK_NAME);
// if the artifact is a -dev jar and "builtBy jar"
return "dev".equals(artifact.getClassifier()) && artifact.getBuildDependencies().getDependencies(null).contains(jarTask);
});
}
}
}
@ApiStatus.Experimental // This is only an api if you squint really hard, expect it to explode every 5 mins. If you must call in afterEvaluate on all projects
public static void setupRemap(Project project, String jarTaskName, String sourcesJarTaskName, String remapJarTaskName, String remapSourcesJarTaskName, String remapAllJarsTaskName, String remapAllSourcesTaskName) {
setupRemap(project, false, jarTaskName, sourcesJarTaskName, remapJarTaskName, remapSourcesJarTaskName, remapAllJarsTaskName, remapAllSourcesTaskName);
}
// isDefaultRemap is set to true for the standard remap task, some defaults are left out when this is false.
private static void setupRemap(Project project, boolean isDefaultRemap, String jarTaskName, String sourcesJarTaskName, String remapJarTaskName, String remapSourcesJarTaskName, String remapAllJarsTaskName, String remapAllSourcesTaskName) {
LoomGradleExtension extension = LoomGradleExtension.get(project);
AbstractArchiveTask jarTask = (AbstractArchiveTask) project.getTasks().getByName(jarTaskName);
RemapJarTask remapJarTask = (RemapJarTask) project.getTasks().findByName(remapJarTaskName);
assert remapJarTask != null;
if (!remapJarTask.getInput().isPresent() && isDefaultRemap) {
jarTask.getArchiveClassifier().convention("dev");
remapJarTask.getArchiveClassifier().convention("");
remapJarTask.getInput().convention(jarTask.getArchiveFile());
}
if (extension.isForge()) {
Set<String> mixinConfigs = PropertyUtil.getAndFinalize(extension.getForge().getMixinConfigs());
if (!mixinConfigs.isEmpty()) {
((Jar) jarTask).manifest(manifest -> {
manifest.attributes(ImmutableMap.of("MixinConfigs", String.join(",", mixinConfigs)));
});
}
}
if (isDefaultRemap) {
extension.getUnmappedModCollection().from(jarTask);
remapJarTask.getAddNestedDependencies().convention(true);
remapJarTask.getRemapAccessWidener().convention(true);
project.getArtifacts().add("archives", remapJarTask);
if (extension.isForge()) {
boolean convertAws = PropertyUtil.getAndFinalize(extension.getForge().getConvertAccessWideners());
if (convertAws) {
Aw2At.setup(project, remapJarTask);
remapJarTask.getRemapAccessWidener().set(false);
}
}
}
remapJarTask.dependsOn(jarTask);
project.getTasks().getByName("build").dependsOn(remapJarTask);
// TODO this might be wrong?
project.getTasks().withType(RemapJarTask.class).forEach(task -> {
if (extension.supportsInclude() && task.getAddNestedDependencies().getOrElse(false)) {
NestedDependencyProvider.getRequiredTasks(project).forEach(task::dependsOn);
}
});
SourceRemapper remapper = null;
// TODO what is this for?
Task parentTask = project.getTasks().getByName("build");
if (extension.getShareRemapCaches().get()) {
Project rootProject = project.getRootProject();
if (extension.isRootProject()) {
SourceRemapper sourceRemapper = new SourceRemapper(rootProject, false);
JarRemapper jarRemapper = new JarRemapper();
remapJarTask.jarRemapper = jarRemapper;
rootProject.getTasks().register(remapAllSourcesTaskName, RemapAllSourcesTask.class, task -> {
task.sourceRemapper = sourceRemapper;
task.doLast(t -> sourceRemapper.remapAll());
});
parentTask = rootProject.getTasks().getByName(remapAllSourcesTaskName);
rootProject.getTasks().register(remapAllJarsTaskName, AbstractLoomTask.class, task -> {
task.doLast(t -> {
try {
jarRemapper.remap(project);
} catch (IOException e) {
throw new RuntimeException("Failed to remap jars", e);
}
});
});
} else {
parentTask = rootProject.getTasks().getByName(remapAllSourcesTaskName);
remapper = ((RemapAllSourcesTask) parentTask).sourceRemapper;
Preconditions.checkNotNull(remapper);
remapJarTask.jarRemapper = ((RemapJarTask) rootProject.getTasks().getByName(remapJarTaskName)).jarRemapper;
project.getTasks().getByName("build").dependsOn(parentTask);
project.getTasks().getByName("build").dependsOn(rootProject.getTasks().getByName(remapAllJarsTaskName));
rootProject.getTasks().getByName(remapAllJarsTaskName).dependsOn(project.getTasks().getByName(remapJarTaskName));
}
}
try {
AbstractArchiveTask sourcesTask = (AbstractArchiveTask) project.getTasks().getByName(sourcesJarTaskName);
RemapSourcesJarTask remapSourcesJarTask = (RemapSourcesJarTask) project.getTasks().findByName(remapSourcesJarTaskName);
Preconditions.checkNotNull(remapSourcesJarTask, "Could not find " + remapSourcesJarTaskName + " in " + project.getName());
remapSourcesJarTask.getOutput().convention(sourcesTask.getArchiveFile().get());
String sourcesTaskClassifer = sourcesTask.getArchiveClassifier().get();
sourcesTask.getArchiveClassifier().set(sourcesTaskClassifer == null ? "dev" : sourcesTaskClassifer + "-dev");
remapSourcesJarTask.getInput().convention(sourcesTask.getArchiveFile());
remapSourcesJarTask.dependsOn(sourcesTask);
if (isDefaultRemap) {
// Do not use lambda here, see: https://github.com/gradle/gradle/pull/17087
//noinspection Convert2Lambda
remapSourcesJarTask.doLast(new Action<>() {
@Override
public void execute(Task task) {
project.getArtifacts().add("archives", remapSourcesJarTask.getOutput());
}
});
}
if (extension.getShareRemapCaches().get()) {
remapSourcesJarTask.setSourceRemapper(remapper);
}
parentTask.dependsOn(remapSourcesJarTask);
} catch (UnknownTaskException ignored) {
// pass
}
}
private static Action<ConfigurablePublishArtifact> artifactConfigurationAction(Task standardTask, String sharedTaskName, Project project) {
LoomGradleExtension extension = LoomGradleExtension.get(project);
return artifact -> {
Task remapTask = standardTask;
if (extension.getShareRemapCaches().get()) {
remapTask = project.getRootProject().getTasks().getByName(sharedTaskName);
}
artifact.builtBy(remapTask);
};
}
}

View File

@@ -37,7 +37,7 @@ import com.google.gson.JsonObject;
import net.fabricmc.loom.util.ZipUtils;
public record AccessWidenerFile(
String name,
String path,
String modId,
byte[] content
) {
@@ -121,7 +121,7 @@ public record AccessWidenerFile(
@Override
public int hashCode() {
int result = Objects.hash(name, modId);
int result = Objects.hash(path, modId);
result = 31 * result + Arrays.hashCode(content);
return result;
}

View File

@@ -34,14 +34,10 @@ import java.util.Arrays;
import com.google.common.hash.Hashing;
import org.gradle.api.Project;
import org.objectweb.asm.commons.Remapper;
import net.fabricmc.accesswidener.AccessWidener;
import net.fabricmc.accesswidener.AccessWidenerReader;
import net.fabricmc.accesswidener.AccessWidenerRemapper;
import net.fabricmc.accesswidener.AccessWidenerWriter;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.processors.JarProcessor;
import net.fabricmc.loom.util.ZipUtils;
@@ -96,25 +92,6 @@ public class AccessWidenerJarProcessor implements JarProcessor {
}
}
/**
* Get this mods access widener remapped to the intermediary namespace.
*/
public byte[] getRemappedAccessWidener(Remapper asmRemapper, String targetNamespace) throws IOException {
int version = AccessWidenerReader.readVersion(modAccessWidener);
AccessWidenerWriter writer = new AccessWidenerWriter(version);
AccessWidenerRemapper remapper = new AccessWidenerRemapper(
writer,
asmRemapper,
MappingsNamespace.NAMED.toString(),
targetNamespace
);
AccessWidenerReader reader = new AccessWidenerReader(remapper);
reader.read(modAccessWidener);
return writer.write();
}
@Override
public boolean isInvalid(File file) {
byte[] hash;

View File

@@ -152,7 +152,7 @@ public class TransitiveAccessWidenerJarProcessor implements JarProcessor {
try {
AccessWidenerRemapper remappingVisitor = new AccessWidenerRemapper(
accessWidener,
tinyRemapper.getRemapper(),
tinyRemapper.getEnvironment().getRemapper(),
MappingsNamespace.INTERMEDIARY.toString(),
MappingsNamespace.NAMED.toString()
);

View File

@@ -24,15 +24,19 @@
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.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import com.google.common.collect.ImmutableMap;
@@ -48,6 +52,9 @@ import org.w3c.dom.Node;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.InstallerData;
import net.fabricmc.loom.configuration.ide.idea.IdeaSyncTask;
import net.fabricmc.loom.configuration.providers.BundleMetadata;
import net.fabricmc.loom.util.Constants;
public class RunConfig {
public String configName;
@@ -57,6 +64,7 @@ public class RunConfig {
public String mainClass;
public String runDirIdeaUrl;
public String runDir;
public String environment;
public List<String> vmArgs = new ArrayList<>();
public List<String> programArgs = new ArrayList<>();
public List<String> vscodeBeforeRun = new ArrayList<>();
@@ -179,6 +187,7 @@ public class RunConfig {
runConfig.runDirIdeaUrl = "file://$PROJECT_DIR$/" + runDir;
runConfig.runDir = runDir;
runConfig.sourceSet = sourceSet;
runConfig.environment = environment;
// Custom parameters
runConfig.programArgs.addAll(settings.getProgramArgs());
@@ -195,7 +204,7 @@ public class RunConfig {
public String fromDummy(String dummy, boolean relativeDir, Project project) throws IOException {
String dummyConfig;
try (InputStream input = SetupIntelijRunConfigs.class.getClassLoader().getResourceAsStream(dummy)) {
try (InputStream input = IdeaSyncTask.class.getClassLoader().getResourceAsStream(dummy)) {
dummyConfig = new String(input.readAllBytes(), StandardCharsets.UTF_8);
}
@@ -249,7 +258,12 @@ public class RunConfig {
}
first = false;
sb.append("\"").append(arg).append("\"");
if (arg.contains(" ")) {
sb.append("\"").append(arg).append("\"");
} else {
sb.append(arg);
}
}
return sb.toString();
@@ -285,6 +299,31 @@ public class RunConfig {
return defaultMainClass;
}
public List<String> getExcludedLibraryPaths(Project project) {
if (!environment.equals("server")) {
return Collections.emptyList();
}
final BundleMetadata bundleMetadata = LoomGradleExtension.get(project).getMinecraftProvider().getServerBundleMetadata();
if (bundleMetadata == null) {
// Legacy version
return Collections.emptyList();
}
final Set<File> allLibraries = project.getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES).getFiles();
final Set<File> serverLibraries = project.getConfigurations().getByName(Constants.Configurations.MINECRAFT_SERVER_DEPENDENCIES).getFiles();
final List<String> clientOnlyLibraries = new LinkedList<>();
for (File commonLibrary : allLibraries) {
if (!serverLibraries.contains(commonLibrary)) {
clientOnlyLibraries.add(commonLibrary.getAbsolutePath());
}
}
return clientOnlyLibraries;
}
private static String encodeEscaped(String s) {
StringBuilder ret = new StringBuilder();

View File

@@ -35,7 +35,7 @@ import java.util.function.Function;
import org.gradle.api.Named;
import org.gradle.api.Project;
import org.gradle.api.plugins.JavaPluginConvention;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.tasks.SourceSet;
import org.jetbrains.annotations.ApiStatus;
@@ -247,10 +247,7 @@ public final class RunConfigSettings implements Named {
}
public void source(String source) {
setSource(proj -> {
JavaPluginConvention conv = proj.getConvention().getPlugin(JavaPluginConvention.class);
return conv.getSourceSets().getByName(source);
});
setSource(proj -> project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets().getByName(source));
}
public void ideConfigGenerated(boolean ideConfigGenerated) {
@@ -261,7 +258,7 @@ public final class RunConfigSettings implements Named {
* Add the {@code -XstartOnFirstThread} JVM argument when on OSX.
*/
public void startFirstThread() {
if (OperatingSystem.getOS().equalsIgnoreCase("osx")) {
if (OperatingSystem.CURRENT_OS.equals(OperatingSystem.MAC_OS)) {
vmArg("-XstartOnFirstThread");
}
}

View File

@@ -1,94 +0,0 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2016-2021 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.ide;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.apache.commons.io.FileUtils;
import org.gradle.api.Project;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftNativesProvider;
import net.fabricmc.loom.configuration.providers.minecraft.assets.MinecraftAssetsProvider;
public class SetupIntelijRunConfigs {
public static void setup(Project project) {
File projectDir = project.getRootProject().file(".idea");
if (!projectDir.exists()) {
return;
}
try {
generate(project, false);
} catch (IOException e) {
throw new RuntimeException("Failed to generate run configs", e);
}
}
public static void generate(Project project, boolean override) throws IOException {
Project rootProject = project.getRootProject();
LoomGradleExtension extension = LoomGradleExtension.get(project);
if (extension.ideSync()) {
//Ensures the assets are downloaded when idea is syncing a project
MinecraftAssetsProvider.provide(extension.getMinecraftProvider(), project);
MinecraftNativesProvider.provide(project);
}
String projectPath = project == rootProject ? "" : project.getPath().replace(':', '_');
File projectDir = rootProject.file(".idea");
File runConfigsDir = new File(projectDir, "runConfigurations");
if (!runConfigsDir.exists()) {
runConfigsDir.mkdirs();
}
for (RunConfigSettings settings : extension.getRunConfigs()) {
if (!settings.isIdeConfigGenerated()) {
continue;
}
RunConfig config = RunConfig.runConfig(project, settings);
String name = config.configName.replaceAll("[^a-zA-Z0-9$_]", "_");
File runConfigs = new File(runConfigsDir, name + projectPath + ".xml");
String runConfigXml = config.fromDummy("idea_run_config_template.xml", true, project);
if (runConfigs.exists() && override) {
runConfigs.delete();
}
if (!runConfigs.exists()) {
FileUtils.writeStringToFile(runConfigs, runConfigXml, StandardCharsets.UTF_8);
}
settings.makeRunDir();
}
}
}

View File

@@ -0,0 +1,47 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2021 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.ide.idea;
import java.util.List;
import org.gradle.api.Project;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.execution.taskgraph.TaskExecutionGraphInternal;
public class IdeaConfiguration {
public static void setup(Project project) {
TaskProvider<IdeaSyncTask> ideaSyncTask = project.getTasks().register("ideaSyncTask", IdeaSyncTask.class, ideaSyncTask1 -> {
ideaSyncTask1.dependsOn(project.getTasks().named("downloadAssets"));
});
if (!IdeaUtils.isIdeaSync()) {
return;
}
// Run the idea sync task, is this exposed via the api?
final TaskExecutionGraphInternal taskGraph = (TaskExecutionGraphInternal) project.getGradle().getTaskGraph();
taskGraph.whenReady(taskExecutionGraph -> taskGraph.addEntryTasks(List.of(ideaSyncTask.get())));
}
}

View File

@@ -0,0 +1,180 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2021 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.ide.idea;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import javax.inject.Inject;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.apache.commons.io.FileUtils;
import org.gradle.api.Project;
import org.gradle.api.tasks.TaskAction;
import org.jetbrains.annotations.VisibleForTesting;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.ide.RunConfig;
import net.fabricmc.loom.configuration.ide.RunConfigSettings;
import net.fabricmc.loom.task.AbstractLoomTask;
public abstract class IdeaSyncTask extends AbstractLoomTask {
@Inject
public IdeaSyncTask() {
// Always re-run this task.
getOutputs().upToDateWhen(element -> false);
}
@TaskAction
public void runTask() throws IOException {
File projectDir = getProject().getRootProject().file(".idea");
if (!projectDir.exists()) {
throw new RuntimeException("No .idea directory found");
}
generateRunConfigs();
}
// See: https://github.com/FabricMC/fabric-loom/pull/206#issuecomment-986054254 for the reason why XML's are still used to provide the run configs
private void generateRunConfigs() throws IOException {
Project rootProject = getProject().getRootProject();
LoomGradleExtension extension = LoomGradleExtension.get(getProject());
String projectPath = getProject() == rootProject ? "" : getProject().getPath().replace(':', '_');
File runConfigsDir = new File(rootProject.file(".idea"), "runConfigurations");
if (!runConfigsDir.exists()) {
runConfigsDir.mkdirs();
}
for (RunConfigSettings settings : extension.getRunConfigs()) {
if (!settings.isIdeConfigGenerated()) {
continue;
}
RunConfig config = RunConfig.runConfig(getProject(), settings);
String name = config.configName.replaceAll("[^a-zA-Z0-9$_]", "_");
File runConfigs = new File(runConfigsDir, name + projectPath + ".xml");
String runConfigXml = config.fromDummy("idea_run_config_template.xml", true, getProject());
if (!runConfigs.exists()) {
FileUtils.writeStringToFile(runConfigs, runConfigXml, StandardCharsets.UTF_8);
}
settings.makeRunDir();
final List<String> excludedLibraryPaths = config.getExcludedLibraryPaths(getProject());
if (!excludedLibraryPaths.isEmpty()) {
try {
setClasspathModifications(runConfigs.toPath(), excludedLibraryPaths);
} catch (Exception e) {
getProject().getLogger().error("Failed to modify run configuration xml", e);
}
}
}
}
private void setClasspathModifications(Path runConfig, List<String> exclusions) throws IOException {
if (!IdeaUtils.supportsCustomizableClasspath()) {
return;
}
final String inputXml = Files.readString(runConfig, StandardCharsets.UTF_8);
final String outputXml;
try {
outputXml = setClasspathModificationsInXml(inputXml, exclusions);
} catch (Exception e) {
getLogger().error("Failed to modify idea xml", e);
return;
}
if (!inputXml.equals(outputXml)) {
Files.writeString(runConfig, outputXml, StandardCharsets.UTF_8);
}
}
@VisibleForTesting
public static String setClasspathModificationsInXml(String input, List<String> exclusions) throws Exception {
final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
final DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
final Document document = documentBuilder.parse(new InputSource(new StringReader(input)));
final Element root = document.getDocumentElement();
final NodeList nodeList = root.getElementsByTagName("configuration");
assert nodeList.getLength() == 1;
final Element configuration = (Element) nodeList.item(0);
final NodeList classpathModificationsList = configuration.getElementsByTagName("classpathModifications");
// Remove all the existing exclusions
for (int i = 0; i < classpathModificationsList.getLength(); i++) {
configuration.removeChild(classpathModificationsList.item(i));
}
final Element classpathModifications = document.createElement("classpathModifications");
for (String exclusionPath : exclusions) {
final Element exclusion = document.createElement("entry");
exclusion.setAttribute("exclude", "true");
exclusion.setAttribute("path", exclusionPath);
classpathModifications.appendChild(exclusion);
}
configuration.appendChild(classpathModifications);
final TransformerFactory transformerFactory = TransformerFactory.newInstance();
final Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
final DOMSource source = new DOMSource(document);
final StringWriter writer = new StringWriter();
transformer.transform(source, new StreamResult(writer));
return writer.toString().replace("\r", "");
}
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2016-2021 FabricMC
* Copyright (c) 2021 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -22,26 +22,24 @@
* SOFTWARE.
*/
package net.fabricmc.loom.build.nesting;
package net.fabricmc.loom.configuration.ide.idea;
import java.io.File;
import java.util.Arrays;
import java.util.Collection;
import java.util.stream.Collectors;
import java.util.Objects;
import org.gradle.api.Project;
public record MergedNestedJarProvider(NestedJarProvider... children) implements NestedJarProvider {
@Override
public Collection<File> provide() {
return Arrays.stream(children)
.map(NestedJarProvider::provide)
.flatMap(Collection::stream)
.collect(Collectors.toList());
public class IdeaUtils {
public static boolean isIdeaSync() {
return Boolean.parseBoolean(System.getProperty("idea.sync.active", "false"));
}
@Override
public void prepare(Project project) {
Arrays.stream(children).forEach(nestedJarProvider -> nestedJarProvider.prepare(project));
public static String getIdeaVersion() {
return Objects.requireNonNull(System.getProperty("idea.version"), "Could not get idea version");
}
// 2021.3 or newer
public static boolean supportsCustomizableClasspath() {
final String[] split = getIdeaVersion().split("\\.");
final int major = Integer.parseInt(split[0]);
final int minor = Integer.parseInt(split[1]);
return major > 2021 || (major == 2021 && minor >= 3);
}
}

View File

@@ -0,0 +1,299 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2021 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.ifaceinject;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.gradle.api.Project;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.commons.Remapper;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.RemappedConfigurationEntry;
import net.fabricmc.loom.configuration.processors.JarProcessor;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.Pair;
import net.fabricmc.loom.util.TinyRemapperHelper;
import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.tinyremapper.TinyRemapper;
public class InterfaceInjectionProcessor implements JarProcessor {
// Filename used to store hash of injected interfaces in processed jar file
private static final String HASH_FILENAME = "injected_interfaces.sha256";
private final Map<String, List<InjectedInterface>> injectedInterfaces;
private final Project project;
private final LoomGradleExtension extension;
private final byte[] inputHash;
private Map<String, List<InjectedInterface>> remappedInjectedInterfaces;
public InterfaceInjectionProcessor(Project project) {
this.project = project;
this.extension = LoomGradleExtension.get(project);
this.injectedInterfaces = getInjectedInterfaces().stream()
.collect(Collectors.groupingBy(InjectedInterface::className));
this.inputHash = hashInjectedInterfaces();
}
public boolean isEmpty() {
return injectedInterfaces.isEmpty();
}
@Override
public void setup() {
}
@Override
public void process(File jarFile) {
// Lazily remap from intermediary->named
if (remappedInjectedInterfaces == null) {
TinyRemapper tinyRemapper = createTinyRemapper();
Remapper remapper = tinyRemapper.getEnvironment().getRemapper();
try {
remappedInjectedInterfaces = new HashMap<>(injectedInterfaces.size());
for (Map.Entry<String, List<InjectedInterface>> entry : injectedInterfaces.entrySet()) {
String namedClassName = remapper.map(entry.getKey());
remappedInjectedInterfaces.put(
namedClassName,
entry.getValue().stream()
.map(injectedInterface ->
new InjectedInterface(
injectedInterface.modId(),
namedClassName,
remapper.map(injectedInterface.ifaceName())
))
.toList()
);
}
} finally {
tinyRemapper.finish();
}
}
project.getLogger().lifecycle("Processing file: " + jarFile.getName());
try {
ZipUtils.transform(jarFile.toPath(), getTransformers());
} catch (IOException e) {
throw new RuntimeException("Failed to apply interface injections to " + jarFile, e);
}
}
private List<Pair<String, ZipUtils.UnsafeUnaryOperator<byte[]>>> getTransformers() {
return remappedInjectedInterfaces.keySet().stream()
.map(string -> new Pair<>(string.replaceAll("\\.", "/") + ".class", getTransformer(string)))
.collect(Collectors.toList());
}
private ZipUtils.UnsafeUnaryOperator<byte[]> getTransformer(String className) {
return input -> {
ClassReader reader = new ClassReader(input);
ClassWriter writer = new ClassWriter(0);
List<InjectedInterface> ifaces = remappedInjectedInterfaces.get(className);
ClassVisitor classVisitor = new InjectingClassVisitor(Constants.ASM_VERSION, writer, ifaces);
// Log which mods add which interface to the class
project.getLogger().info("Injecting interfaces into " + className + ": "
+ ifaces.stream().map(i -> i.ifaceName() + " [" + i.modId() + "]"
).collect(Collectors.joining(", ")));
reader.accept(classVisitor, 0);
return writer.toByteArray();
};
}
@Override
public boolean isInvalid(File file) {
byte[] hash;
try {
hash = ZipUtils.unpackNullable(file.toPath(), HASH_FILENAME);
} catch (IOException e) {
return true;
}
if (hash == null) {
return true;
}
return !Arrays.equals(inputHash, hash);
}
private List<InjectedInterface> getInjectedInterfaces() {
List<InjectedInterface> result = new ArrayList<>();
for (RemappedConfigurationEntry entry : Constants.MOD_COMPILE_ENTRIES) {
// Only apply injected interfaces from mods that are part of the compile classpath
if (!entry.compileClasspath()) {
continue;
}
Set<File> artifacts = extension.getLazyConfigurationProvider(entry.sourceConfiguration())
.get()
.resolve();
for (File artifact : artifacts) {
result.addAll(InjectedInterface.fromModJar(artifact.toPath()));
}
}
return result;
}
private record InjectedInterface(String modId, String className, String ifaceName) {
/**
* Reads the injected interfaces contained in a mod jar, or returns null if there is none.
*/
public static List<InjectedInterface> fromModJar(Path modJarPath) {
byte[] modJsonBytes;
try {
modJsonBytes = ZipUtils.unpackNullable(modJarPath, "fabric.mod.json");
} catch (IOException e) {
throw new RuntimeException("Failed to extract fabric.mod.json from " + modJarPath);
}
if (modJsonBytes == null) {
return Collections.emptyList();
}
JsonObject jsonObject = new Gson().fromJson(new String(modJsonBytes, StandardCharsets.UTF_8), JsonObject.class);
String modId = jsonObject.get("id").getAsString();
if (!jsonObject.has("custom")) {
return Collections.emptyList();
}
JsonObject custom = jsonObject.getAsJsonObject("custom");
if (!custom.has("loom:injected_interfaces")) {
return Collections.emptyList();
}
JsonObject addedIfaces = custom.getAsJsonObject("loom:injected_interfaces");
List<InjectedInterface> result = new ArrayList<>();
for (String className : addedIfaces.keySet()) {
JsonArray ifaceNames = addedIfaces.getAsJsonArray(className);
for (JsonElement ifaceName : ifaceNames) {
result.add(new InjectedInterface(modId, className, ifaceName.getAsString()));
}
}
return result;
}
}
private static class InjectingClassVisitor extends ClassVisitor {
private final List<InjectedInterface> injectedInterfaces;
InjectingClassVisitor(int asmVersion, ClassWriter writer, List<InjectedInterface> injectedInterfaces) {
super(asmVersion, writer);
this.injectedInterfaces = injectedInterfaces;
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
Set<String> modifiedInterfaces = new LinkedHashSet<>(interfaces.length + injectedInterfaces.size());
Collections.addAll(modifiedInterfaces, interfaces);
for (InjectedInterface injectedInterface : injectedInterfaces) {
modifiedInterfaces.add(injectedInterface.ifaceName());
}
// See JVMS: https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-4.html#jvms-ClassSignature
if (signature != null) {
var resultingSignature = new StringBuilder(signature);
for (InjectedInterface injectedInterface : injectedInterfaces) {
String superinterfaceSignature = "L" + injectedInterface.ifaceName() + ";";
if (resultingSignature.indexOf(superinterfaceSignature) == -1) {
resultingSignature.append(superinterfaceSignature);
}
}
signature = resultingSignature.toString();
}
super.visit(version, access, name, signature, superName, modifiedInterfaces.toArray(new String[0]));
}
}
private TinyRemapper createTinyRemapper() {
try {
TinyRemapper tinyRemapper = TinyRemapperHelper.getTinyRemapper(project, "intermediary", "named");
tinyRemapper.readClassPath(TinyRemapperHelper.getMinecraftDependencies(project));
tinyRemapper.readClassPath(extension.getMinecraftMappedProvider().getIntermediaryJar().toPath());
return tinyRemapper;
} catch (IOException e) {
throw new RuntimeException("Failed to create tiny remapper for intermediary->named", e);
}
}
private byte[] hashInjectedInterfaces() {
// Hash the interfaces we're about to inject to not have to repeat this everytime
Hasher hasher = Hashing.sha256().newHasher();
for (Map.Entry<String, List<InjectedInterface>> entry : injectedInterfaces.entrySet()) {
hasher.putString("class:", StandardCharsets.UTF_8);
hasher.putString(entry.getKey(), StandardCharsets.UTF_8);
for (InjectedInterface ifaceName : entry.getValue()) {
hasher.putString("iface:", StandardCharsets.UTF_8);
hasher.putString(ifaceName.ifaceName(), StandardCharsets.UTF_8);
}
}
return hasher.hash().asBytes();
}
}

View File

@@ -30,7 +30,7 @@ import java.io.IOException;
import com.google.gson.JsonObject;
import org.gradle.api.Project;
import org.gradle.api.plugins.JavaPluginConvention;
import org.gradle.api.plugins.JavaPluginExtension;
import net.fabricmc.loom.LoomGradlePlugin;
@@ -67,7 +67,7 @@ public class ModVersionParser {
}
private File locateModJsonFile() {
return project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets()
return project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets()
.getByName("main")
.getResources()
.matching(patternFilterable -> patternFilterable.include("fabric.mod.json"))

View File

@@ -0,0 +1,93 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2021 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.providers;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.util.FileSystemUtil;
public record BundleMetadata(List<Entry> libraries, List<Entry> versions, String mainClass) {
private static final String LIBRARIES_LIST_PATH = "META-INF/libraries.list";
private static final String VERSIONS_LIST_PATH = "META-INF/versions.list";
private static final String MAINCLASS_PATH = "META-INF/main-class";
@Nullable
public static BundleMetadata fromJar(Path jar) throws IOException {
final List<Entry> libraries;
final List<Entry> versions;
final String mainClass;
try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(jar)) {
if (!Files.exists(fs.get().getPath(VERSIONS_LIST_PATH))) {
// Legacy jar
return null;
}
libraries = readEntries(fs.readString(LIBRARIES_LIST_PATH), "META-INF/libraries/");
versions = readEntries(fs.readString(VERSIONS_LIST_PATH), "META-INF/versions/");
mainClass = fs.readString(MAINCLASS_PATH).trim();
}
return new BundleMetadata(libraries, versions, mainClass);
}
private static List<Entry> readEntries(String content, String pathPrefix) {
List<Entry> entries = new ArrayList<>();
for (String entry : content.split("\n")) {
if (entry.isBlank()) {
continue;
}
String[] split = entry.split("\t");
if (split.length != 3) {
continue;
}
entries.add(new Entry(split[0], split[1], pathPrefix + split[2]));
}
return Collections.unmodifiableList(entries);
}
public record Entry(String sha1, String name, String path) {
public void unpackEntry(Path jar, Path dest) throws IOException {
try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(jar);
InputStream is = Files.newInputStream(fs.get().getPath(path()))) {
Files.copy(is, dest, StandardCopyOption.REPLACE_EXISTING);
}
}
}
}

View File

@@ -57,14 +57,16 @@ public class LaunchProvider extends DependencyProvider {
@Override
public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws IOException {
final String nativesPath = getExtension().getFiles().getNativesDirectory(getProject()).getAbsolutePath();
final LaunchConfig launchConfig = new LaunchConfig()
.property("fabric.development", "true")
.property("fabric.remapClasspathFile", getRemapClasspathFile().getAbsolutePath())
.property("log4j.configurationFile", getAllLog4JConfigFiles())
.property("log4j2.formatMsgNoLookups", "true")
.property("client", "java.library.path", getExtension().getMinecraftProvider().nativesDir().getAbsolutePath())
.property("client", "org.lwjgl.librarypath", getExtension().getMinecraftProvider().nativesDir().getAbsolutePath());
.property("client", "java.library.path", nativesPath)
.property("client", "org.lwjgl.librarypath", nativesPath);
if (!getExtension().isForge()) {
launchConfig

View File

@@ -31,10 +31,6 @@ import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta;
public interface MinecraftProvider {
File workingDir();
boolean hasCustomNatives();
File nativesDir();
File dir(String path);
File file(String path);

View File

@@ -27,21 +27,17 @@ package net.fabricmc.loom.configuration.providers;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.StandardCopyOption;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import com.google.common.base.Stopwatch;
import com.google.common.io.Files;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
import org.gradle.api.logging.Logger;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.configuration.DependencyProvider;
@@ -49,10 +45,9 @@ import net.fabricmc.loom.configuration.providers.minecraft.ManifestVersion;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftLibraryProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.MirrorUtil;
import net.fabricmc.loom.util.DownloadUtil;
import net.fabricmc.loom.util.HashedDownloadUtil;
import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.loom.util.MirrorUtil;
import net.fabricmc.stitch.merge.JarMerger;
public class MinecraftProviderImpl extends DependencyProvider implements MinecraftProvider {
@@ -68,6 +63,8 @@ public class MinecraftProviderImpl extends DependencyProvider implements Minecra
public File minecraftServerJar;
// The extracted server jar from the boostrap, only exists in >=21w39a
public File minecraftExtractedServerJar;
@Nullable
private BundleMetadata serverBundleMetadata;
private Boolean isNewerThan21w39a;
private File minecraftMergedJar;
private File versionManifestJson;
@@ -109,6 +106,8 @@ public class MinecraftProviderImpl extends DependencyProvider implements Minecra
downloadJars(getProject().getLogger());
}
serverBundleMetadata = BundleMetadata.fromJar(minecraftServerJar.toPath());
libraryProvider = new MinecraftLibraryProvider();
libraryProvider.provide(this, getProject());
@@ -274,7 +273,20 @@ public class MinecraftProviderImpl extends DependencyProvider implements Minecra
logger.info(":merging jars");
Stopwatch stopwatch = Stopwatch.createStarted();
try (JarMerger jarMerger = new JarMerger(minecraftClientJar, getServerJarToMerge(logger), minecraftMergedJar)) {
File jarToMerge = minecraftServerJar;
if (serverBundleMetadata != null) {
logger.info(":Extracting server jar from bootstrap");
if (serverBundleMetadata.versions().size() != 1) {
throw new UnsupportedOperationException("Expected only 1 version in META-INF/versions.list, but got %d".formatted(serverBundleMetadata.versions().size()));
}
serverBundleMetadata.versions().get(0).unpackEntry(minecraftServerJar.toPath(), minecraftExtractedServerJar.toPath());
jarToMerge = minecraftExtractedServerJar;
}
try (JarMerger jarMerger = new JarMerger(minecraftClientJar, jarToMerge, minecraftMergedJar)) {
jarMerger.enableSyntheticParamsOffset();
jarMerger.merge();
}
@@ -282,78 +294,6 @@ public class MinecraftProviderImpl extends DependencyProvider implements Minecra
logger.info(":merged jars in " + stopwatch);
}
private File getServerJarToMerge(Logger logger) throws IOException {
try (ZipFile zipFile = new ZipFile(minecraftServerJar)) {
ZipEntry versionsListEntry = zipFile.getEntry("META-INF/versions.list");
if (versionsListEntry == null) {
// Legacy pre 21w38a jar
return minecraftServerJar;
}
logger.info(":Extracting server jar from bootstrap");
String versionsList;
try (InputStream is = zipFile.getInputStream(versionsListEntry)) {
versionsList = new String(is.readAllBytes(), StandardCharsets.UTF_8);
}
String jarPath = null;
String[] versions = versionsList.split("\n");
if (versions.length != 1) {
throw new UnsupportedOperationException("Expected only 1 version in META-INF/versions.list, but got %d".formatted(versions.length));
}
for (String version : versions) {
if (version.isBlank()) continue;
String[] split = version.split("\t");
if (split.length != 3) continue;
final String hash = split[0];
final String id = split[1];
final String path = split[2];
// Take the first (only) version we find.
jarPath = path;
break;
}
Objects.requireNonNull(jarPath, "Could not find minecraft server jar for " + minecraftVersion());
ZipEntry serverJarEntry = zipFile.getEntry("META-INF/versions/" + jarPath);
Objects.requireNonNull(serverJarEntry, "Could not find server jar in boostrap@ " + jarPath);
try (InputStream is = zipFile.getInputStream(serverJarEntry)) {
java.nio.file.Files.copy(is, minecraftExtractedServerJar.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
return minecraftExtractedServerJar;
}
}
public File getMinecraftServerJar() {
if (isNewerThan21w39a()) {
try {
return getServerJarToMerge(getProject().getLogger());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return minecraftServerJar;
}
public boolean isNewerThan21w39a() {
if (isNewerThan21w39a != null) {
return isNewerThan21w39a;
}
return isNewerThan21w39a = ZipUtils.contains(minecraftServerJar.toPath(), "META-INF/versions.list");
}
public File getMergedJar() {
return minecraftMergedJar;
}
@@ -363,20 +303,6 @@ public class MinecraftProviderImpl extends DependencyProvider implements Minecra
return workingDir;
}
@Override
public boolean hasCustomNatives() {
return getProject().getProperties().get("fabric.loom.natives.dir") != null;
}
@Override
public File nativesDir() {
if (hasCustomNatives()) {
return new File((String) getProject().property("fabric.loom.natives.dir"));
}
return dir("natives");
}
@Override
public File dir(String path) {
File dir = file(path);
@@ -415,4 +341,9 @@ public class MinecraftProviderImpl extends DependencyProvider implements Minecra
public String getTargetConfig() {
return Constants.Configurations.MINECRAFT;
}
@Nullable
public BundleMetadata getServerBundleMetadata() {
return serverBundleMetadata;
}
}

View File

@@ -61,6 +61,7 @@ import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.DependencyProvider;
import net.fabricmc.loom.configuration.accesswidener.AccessWidenerJarProcessor;
import net.fabricmc.loom.configuration.accesswidener.TransitiveAccessWidenerJarProcessor;
import net.fabricmc.loom.configuration.ifaceinject.InterfaceInjectionProcessor;
import net.fabricmc.loom.configuration.processors.JarProcessorManager;
import net.fabricmc.loom.configuration.processors.MinecraftProcessedProvider;
import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl;
@@ -212,6 +213,14 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
}
}
if (extension.getEnableInterfaceInjection().get()) {
InterfaceInjectionProcessor jarProcessor = new InterfaceInjectionProcessor(getProject());
if (!jarProcessor.isEmpty()) {
extension.getGameJarProcessors().add(jarProcessor);
}
}
extension.getAccessWidenerPath().finalizeValue();
extension.getGameJarProcessors().finalizeValue();
JarProcessorManager processorManager = new JarProcessorManager(extension.getGameJarProcessors().get());
@@ -275,7 +284,7 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
String yarnMinecraftVersion = yarnVersion.substring(0, yarnVersion.lastIndexOf(separator));
if (!yarnMinecraftVersion.equalsIgnoreCase(minecraftVersion)) {
throw new RuntimeException(String.format("Minecraft Version (%s) does not match yarn's minecraft version (%s)", minecraftVersion, yarnMinecraftVersion));
getProject().getLogger().warn("Minecraft Version ({}) does not match yarn's minecraft version ({})", minecraftVersion, yarnMinecraftVersion);
}
// We can save reading the zip file + header by checking the file name
@@ -625,6 +634,10 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
}
}
public String getBuildServiceName(String name, String from, String to) {
return "%s:%s:%s>%S".formatted(name, mappingsIdentifier(), from, to);
}
public record UnpickMetadata(String unpickGroup, String unpickVersion) {
}
}

View File

@@ -0,0 +1,108 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2021 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.providers.minecraft;
import static net.fabricmc.loom.util.OperatingSystem.LINUX;
import static net.fabricmc.loom.util.OperatingSystem.MAC_OS;
import static net.fabricmc.loom.util.OperatingSystem.WINDOWS;
import java.util.List;
import org.gradle.api.Project;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.util.Architecture;
import net.fabricmc.loom.util.OperatingSystem;
public class LWJGLVersionOverride {
public static final String LWJGL_VERSION = "3.3.0";
@Nullable
public static final String NATIVE_CLASSIFIER = getNativesClassifier();
public static final List<String> DEPENDENCIES = List.of(
"org.lwjgl:lwjgl:" + LWJGL_VERSION,
"org.lwjgl:lwjgl-glfw:" + LWJGL_VERSION,
"org.lwjgl:lwjgl-jemalloc:" + LWJGL_VERSION,
"org.lwjgl:lwjgl-openal:" + LWJGL_VERSION,
"org.lwjgl:lwjgl-opengl:" + LWJGL_VERSION,
"org.lwjgl:lwjgl-stb:" + LWJGL_VERSION,
"org.lwjgl:lwjgl-tinyfd:" + LWJGL_VERSION
);
public static final List<String> NATIVES = DEPENDENCIES.stream().map(s -> s + ":" + NATIVE_CLASSIFIER).toList();
/**
* Update lwjgl by default when running on arm and a supported configuration.
*/
public static boolean overrideByDefault() {
return NATIVE_CLASSIFIER != null && Architecture.CURRENT.isArm();
}
public static boolean forceOverride(Project project) {
return project.getProperties().get("fabric.loom.override-lwjgl") != null;
}
@Nullable
private static String getNativesClassifier() {
return switch (OperatingSystem.CURRENT_OS) {
case WINDOWS -> getWindowsClassifier();
case MAC_OS -> getMacOSClassifier();
case LINUX -> getLinuxClassifier();
default -> null;
};
}
private static String getWindowsClassifier() {
if (Architecture.CURRENT.is64Bit()) {
if (Architecture.CURRENT.isArm()) {
// Arm 64 bit
return "natives-windows-arm64";
}
// None arm 64bit
return "natives-windows";
}
// All 32bit, including arm
return "natives-windows-x86";
}
private static String getMacOSClassifier() {
if (Architecture.CURRENT.isArm()) {
// Apple silicone arm
return "natives-macos-arm64";
}
// Intel 64bit.
return "natives-macos";
}
private static String getLinuxClassifier() {
if (Architecture.CURRENT.isArm()) {
return Architecture.CURRENT.is64Bit() ? "natives-linux-arm64" : "natives-linux-arm32";
}
return "natives-linux";
}
}

View File

@@ -24,31 +24,77 @@
package net.fabricmc.loom.configuration.providers.minecraft;
import java.io.File;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.gradle.api.Project;
import org.gradle.api.artifacts.ExternalModuleDependency;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.providers.BundleMetadata;
import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl;
import net.fabricmc.loom.util.Constants;
public class MinecraftLibraryProvider {
public File MINECRAFT_LIBS;
private static final Pattern NATIVES_PATTERN = Pattern.compile("^(?<group>.*)/(.*?)/(?<version>.*)/((?<name>.*?)-([0-9].*?)-)(?<classifier>.*).jar$");
public void provide(MinecraftProviderImpl minecraftProvider, Project project) {
MinecraftVersionMeta versionInfo = minecraftProvider.getVersionInfo();
BundleMetadata serverBundleMetadata = minecraftProvider.getServerBundleMetadata();
initFiles(project, minecraftProvider);
final boolean overrideLWJGL = LWJGLVersionOverride.overrideByDefault() || LWJGLVersionOverride.forceOverride(project) || Boolean.getBoolean("loom.test.lwjgloverride");
if (overrideLWJGL) {
project.getLogger().warn("Loom is upgrading Minecraft's LWJGL version to {}", LWJGLVersionOverride.LWJGL_VERSION);
}
for (MinecraftVersionMeta.Library library : versionInfo.libraries()) {
if (library.isValidForOS() && !library.hasNatives() && library.artifact() != null) {
project.getDependencies().add(Constants.Configurations.MINECRAFT_DEPENDENCIES, project.getDependencies().module(library.name()));
if (overrideLWJGL && library.name().startsWith("org.lwjgl")) {
continue;
}
if (library.isValidForOS() && !library.hasNatives() && library.artifact() != null) {
if (serverBundleMetadata != null && isLibraryInBundle(serverBundleMetadata, library)) {
project.getDependencies().add(Constants.Configurations.MINECRAFT_SERVER_DEPENDENCIES, library.name());
} else {
// Client only library, or legacy version
project.getDependencies().add(Constants.Configurations.MINECRAFT_DEPENDENCIES, library.name());
}
}
if (library.hasNativesForOS()) {
MinecraftVersionMeta.Download nativeDownload = library.classifierForOS();
Matcher matcher = NATIVES_PATTERN.matcher(nativeDownload.path());
if (!matcher.find()) {
project.getLogger().warn("Failed to match regex for natives path : " + nativeDownload.path());
continue;
}
final String group = matcher.group("group").replace("/", ".");
final String name = matcher.group("name");
final String version = matcher.group("version");
final String classifier = matcher.group("classifier");
final String dependencyNotation = "%s:%s:%s:%s".formatted(group, name, version, classifier);
project.getLogger().debug("Add native dependency '{}'", dependencyNotation);
project.getDependencies().add(Constants.Configurations.MINECRAFT_NATIVES, dependencyNotation);
}
}
if (overrideLWJGL) {
LWJGLVersionOverride.DEPENDENCIES.forEach(s -> project.getDependencies().add(Constants.Configurations.MINECRAFT_DEPENDENCIES, s));
LWJGLVersionOverride.NATIVES.forEach(s -> project.getDependencies().add(Constants.Configurations.MINECRAFT_NATIVES, s));
// Add the native support mod that fixes a handful of issues related to the LWJGL update at runtime.
ExternalModuleDependency dependency = (ExternalModuleDependency) project.getDependencies().create(Constants.Dependencies.NATIVE_SUPPORT + Constants.Dependencies.Versions.NATIVE_SUPPORT_VERSION);
dependency.setTransitive(false);
project.getDependencies().add("modLocalRuntime", dependency);
}
}
private void initFiles(Project project, MinecraftProviderImpl minecraftProvider) {
LoomGradleExtension extension = LoomGradleExtension.get(project);
MINECRAFT_LIBS = new File(extension.getFiles().getUserCache(), "libraries");
private static boolean isLibraryInBundle(BundleMetadata bundleMetadata, MinecraftVersionMeta.Library library) {
return bundleMetadata.libraries().stream().anyMatch(entry -> entry.name().equals(library.name()));
}
}

View File

@@ -1,150 +0,0 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2016-2021 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.providers.minecraft;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.apache.commons.io.FileUtils;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.util.HashedDownloadUtil;
import net.fabricmc.loom.util.MirrorUtil;
import net.fabricmc.loom.util.ZipUtils;
public class MinecraftNativesProvider {
private final Project project;
private final LoomGradleExtension extension;
private final File nativesDir;
private final File jarStore;
public MinecraftNativesProvider(Project project) {
this.project = project;
extension = LoomGradleExtension.get(project);
nativesDir = extension.getMinecraftProvider().nativesDir();
jarStore = extension.getFiles().getNativesJarStore();
}
public static void provide(Project project) throws IOException {
new MinecraftNativesProvider(project).provide();
}
private void provide() throws IOException {
if (extension.getMinecraftProvider().hasCustomNatives()) {
if (!nativesDir.exists()) {
throw new RuntimeException("Could no find custom natives directory at " + nativesDir.getAbsolutePath());
}
return;
}
if (!LoomGradlePlugin.refreshDeps && !requiresExtract()) {
project.getLogger().info("Natives do no need extracting, skipping");
return;
}
extractNatives();
}
private void extractNatives() throws IOException {
boolean offline = project.getGradle().getStartParameter().isOffline();
if (nativesDir.exists()) {
try {
FileUtils.deleteDirectory(nativesDir);
} catch (IOException e) {
throw new IOException("Failed to delete the natives directory, is the game running?", e);
}
}
nativesDir.mkdirs();
for (MinecraftVersionMeta.Download library : getNatives()) {
File libJarFile = library.relativeFile(jarStore);
if (!offline) {
HashedDownloadUtil.downloadIfInvalid(new URL(MirrorUtil.getLibrariesBase(project) + library.path()), libJarFile, library.sha1(), project.getLogger(), false);
}
if (!libJarFile.exists()) {
throw new GradleException("Native jar not found at " + libJarFile.getAbsolutePath());
}
ZipUtils.unpackAll(libJarFile.toPath(), nativesDir.toPath());
// Store a file containing the hash of the extracted natives, used on subsequent runs to skip extracting all the natives if they haven't changed
File libSha1File = new File(nativesDir, libJarFile.getName() + ".sha1");
FileUtils.writeStringToFile(libSha1File, library.sha1(), StandardCharsets.UTF_8);
}
}
private boolean requiresExtract() {
List<MinecraftVersionMeta.Download> natives = getNatives();
if (natives.isEmpty()) {
throw new IllegalStateException("No natives found for the current system");
}
for (MinecraftVersionMeta.Download library : natives) {
File libJarFile = library.relativeFile(jarStore);
File libSha1File = new File(nativesDir, libJarFile.getName() + ".sha1");
if (!libSha1File.exists()) {
return true;
}
try {
String sha1 = FileUtils.readFileToString(libSha1File, StandardCharsets.UTF_8);
if (!sha1.equalsIgnoreCase(library.sha1())) {
return true;
}
} catch (IOException e) {
project.getLogger().error("Failed to read " + libSha1File.getAbsolutePath(), e);
return true;
}
}
// All looks good, no need to re-extract
return false;
}
private List<MinecraftVersionMeta.Download> getNatives() {
return extension.getMinecraftProvider().getVersionInfo().libraries().stream()
.filter((MinecraftVersionMeta.Library::hasNativesForOS))
.map(MinecraftVersionMeta.Library::classifierForOS)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
}

View File

@@ -81,7 +81,7 @@ public record MinecraftVersionMeta(
return false;
}
if (natives.get(OperatingSystem.getOS()) == null) {
if (natives.get(OperatingSystem.CURRENT_OS) == null) {
return false;
}
@@ -89,7 +89,7 @@ public record MinecraftVersionMeta(
}
public Download classifierForOS() {
return downloads().classifier(natives.get(OperatingSystem.getOS()));
return downloads().classifier(natives.get(OperatingSystem.CURRENT_OS));
}
public Download artifact() {
@@ -119,7 +119,7 @@ public record MinecraftVersionMeta(
public record OS(String name) {
public boolean isValidForOS() {
return name() == null || name().equalsIgnoreCase(OperatingSystem.getOS());
return name() == null || name().equalsIgnoreCase(OperatingSystem.CURRENT_OS);
}
}

View File

@@ -0,0 +1,85 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2021 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.providers.minecraft;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.gradle.api.Project;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.commons.Remapper;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.TinyRemapperHelper;
import net.fabricmc.tinyremapper.TinyRemapper;
import net.fabricmc.tinyremapper.api.TrClass;
public record SignatureFixerApplyVisitor(Map<String, String> signatureFixes) implements TinyRemapper.ApplyVisitorProvider {
@Override
public ClassVisitor insertApplyVisitor(TrClass cls, ClassVisitor next) {
return new ClassVisitor(Constants.ASM_VERSION, next) {
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
if (signature == null) {
signature = signatureFixes.getOrDefault(name, null);
}
super.visit(version, access, name, signature, superName, interfaces);
}
};
}
public static Map<String, String> getRemappedSignatures(boolean toIntermediary, MappingsProviderImpl mappingsProvider, Project project, String targetNamespace) throws IOException {
if (mappingsProvider.getSignatureFixes() == null) {
// No fixes
return Collections.emptyMap();
}
if (toIntermediary) {
// No need to remap, as these are already intermediary
return mappingsProvider.getSignatureFixes();
}
// Remap the sig fixes from intermediary to the target namespace
final Map<String, String> remapped = new HashMap<>();
final TinyRemapper sigTinyRemapper = TinyRemapperHelper.getTinyRemapper(project, MappingsNamespace.INTERMEDIARY.toString(), targetNamespace);
final Remapper sigAsmRemapper = sigTinyRemapper.getEnvironment().getRemapper();
// Remap the class names and the signatures using a new tiny remapper instance.
for (Map.Entry<String, String> entry : mappingsProvider.getSignatureFixes().entrySet()) {
remapped.put(
sigAsmRemapper.map(entry.getKey()),
sigAsmRemapper.mapSignature(entry.getValue(), false)
);
}
sigTinyRemapper.finish();
return remapped;
}
}

View File

@@ -43,7 +43,7 @@ public interface LoomFiles {
File getProjectPersistentCache();
File getProjectBuildCache();
File getRemappedModCache();
File getNativesJarStore();
File getNativesDirectory(Project project);
File getDefaultLog4jConfigFile();
File getDevLauncherConfig();
File getUnpickLoggingConfigFile();

View File

@@ -26,6 +26,10 @@ package net.fabricmc.loom.extension;
import java.io.File;
import org.gradle.api.Project;
import net.fabricmc.loom.LoomGradleExtension;
public abstract class LoomFilesBaseImpl implements LoomFiles {
protected abstract File getGradleUserHomeDir();
protected abstract File getRootDir();
@@ -70,8 +74,8 @@ public abstract class LoomFilesBaseImpl implements LoomFiles {
}
@Override
public File getNativesJarStore() {
return createFile(getUserCache(), "natives/jars");
public File getNativesDirectory(Project project) {
return createFile(getRootProjectPersistentCache(), "natives/" + LoomGradleExtension.get(project).getMinecraftProvider().minecraftVersion());
}
@Override

View File

@@ -33,6 +33,7 @@ import java.util.function.Consumer;
import com.google.common.base.Suppliers;
import org.gradle.api.Action;
import org.gradle.api.DomainObjectCollection;
import org.gradle.api.NamedDomainObjectContainer;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Dependency;
@@ -69,7 +70,7 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
private static final String PLATFORM_PROPERTY = "loom.platform";
protected final DeprecationHelper deprecationHelper;
protected final ListProperty<LoomDecompiler> decompilers;
protected final DomainObjectCollection<LoomDecompiler> decompilers;
protected final ListProperty<JarProcessor> jarProcessors;
protected final ConfigurableFileCollection log4jConfigs;
protected final RegularFileProperty accessWidener;
@@ -79,6 +80,7 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
protected final Property<Boolean> setupRemappedVariants;
protected final Property<Boolean> transitiveAccessWideners;
protected final Property<String> intermediary;
protected final Property<Boolean> enableInterfaceInjection;
private final ModVersionParser versionParser;
@@ -98,8 +100,7 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
protected LoomGradleExtensionApiImpl(Project project, LoomFiles directories) {
this.runConfigs = project.container(RunConfigSettings.class,
baseName -> new RunConfigSettings(project, baseName));
this.decompilers = project.getObjects().listProperty(LoomDecompiler.class)
.empty();
this.decompilers = project.getObjects().domainObjectSet(LoomDecompiler.class);
this.jarProcessors = project.getObjects().listProperty(JarProcessor.class)
.empty();
this.log4jConfigs = project.files(directories.getDefaultLog4jConfigFile());
@@ -116,6 +117,9 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
this.transitiveAccessWideners.finalizeValueOnRead();
this.intermediary = project.getObjects().property(String.class)
.convention("https://maven.fabricmc.net/net/fabricmc/intermediary/%1$s/intermediary-%1$s-v2.jar");
this.enableInterfaceInjection = project.getObjects().property(Boolean.class)
.convention(true);
this.enableInterfaceInjection.finalizeValueOnRead();
this.versionParser = new ModVersionParser(project);
@@ -158,7 +162,7 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
}
@Override
public ListProperty<LoomDecompiler> getGameDecompilers() {
public DomainObjectCollection<LoomDecompiler> getGameDecompilers() {
return decompilers;
}
@@ -231,6 +235,11 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
return intermediary;
}
@Override
public Property<Boolean> getEnableInterfaceInjection() {
return enableInterfaceInjection;
}
@Override
public void disableDeprecatedPomGeneration(MavenPublication publication) {
net.fabricmc.loom.configuration.MavenPublication.excludePublication(publication);

View File

@@ -26,22 +26,22 @@ package net.fabricmc.loom.extension;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Supplier;
import com.google.common.base.Suppliers;
import org.cadixdev.lorenz.MappingSet;
import org.cadixdev.mercury.Mercury;
import org.gradle.api.Action;
import org.gradle.api.NamedDomainObjectProvider;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.FileCollection;
import org.gradle.api.tasks.SourceSet;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.ForgeExtensionAPI;
@@ -59,7 +59,7 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
private final ConfigurableFileCollection unmappedMods;
private final Supplier<ForgeExtensionAPI> forgeExtension;
private final Set<File> mixinMappings = Collections.synchronizedSet(new HashSet<>());
private final ConfigurableFileCollection mixinMappings;
private final MappingSet[] srcMappingCache = new MappingSet[2];
private final Mercury[] srcMercuryCache = new Mercury[2];
private final Map<String, NamedDomainObjectProvider<Configuration>> lazyConfigurations = new HashMap<>();
@@ -80,6 +80,7 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
this.project = project;
// Initiate with newInstance to allow gradle to decorate our extension
this.mixinApExtension = project.getObjects().newInstance(MixinExtensionImpl.class, project);
this.mixinMappings = project.getObjects().fileCollection();
this.loomFiles = files;
this.unmappedMods = project.files();
this.forgeExtension = Suppliers.memoize(() -> isForge() ? project.getObjects().newInstance(ForgeExtensionImpl.class, project, this) : null);
@@ -97,15 +98,15 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
}
@Override
public synchronized File getNextMixinMappings() {
File mixinMapping = new File(getFiles().getProjectBuildCache(), "mixin-map-" + getMappingsProvider().mappingsIdentifier() + "." + mixinMappings.size() + ".tiny");
mixinMappings.add(mixinMapping);
public synchronized File getMixinMappings(SourceSet sourceSet) {
File mixinMapping = new File(getFiles().getProjectBuildCache(), "mixin-map-" + getMappingsProvider().mappingsIdentifier() + "." + sourceSet.getName() + ".tiny");
mixinMappings.from(getProject().files(mixinMapping));
return mixinMapping;
}
@Override
public Set<File> getAllMixinMappings() {
return mixinMappings;
public FileCollection getAllMixinMappings() {
return mixinMappings.filter(File::exists);
}
@Override
@@ -160,8 +161,8 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
}
@Override
public NamedDomainObjectProvider<Configuration> createLazyConfiguration(String name) {
NamedDomainObjectProvider<Configuration> provider = project.getConfigurations().register(name);
public NamedDomainObjectProvider<Configuration> createLazyConfiguration(String name, Action<? super Configuration> configurationAction) {
NamedDomainObjectProvider<Configuration> provider = project.getConfigurations().register(name, configurationAction);
if (lazyConfigurations.containsKey(name)) {
throw new IllegalStateException("Duplicate configuration name" + name);

View File

@@ -29,7 +29,7 @@ import java.util.Objects;
import org.gradle.api.Action;
import org.gradle.api.InvalidUserDataException;
import org.gradle.api.Project;
import org.gradle.api.plugins.JavaPluginConvention;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.SourceSet;
@@ -111,8 +111,7 @@ public abstract class MixinExtensionApiImpl implements MixinExtensionAPI {
private SourceSet resolveSourceSet(String sourceSetName) {
// try to find sourceSet with name sourceSetName in this project
SourceSet sourceSet = project.getConvention().getPlugin(JavaPluginConvention.class)
.getSourceSets().findByName(sourceSetName);
SourceSet sourceSet = project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets().findByName(sourceSetName);
if (sourceSet == null) {
throw new InvalidUserDataException("No sourceSet " + sourceSetName + " was found");

View File

@@ -38,8 +38,8 @@ import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.UnknownTaskException;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.plugins.BasePluginConvention;
import org.gradle.api.plugins.JavaPluginConvention;
import org.gradle.api.plugins.BasePluginExtension;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.Input;
@@ -67,12 +67,11 @@ public class MixinExtensionImpl extends MixinExtensionApiImpl implements MixinEx
}
private String getDefaultMixinRefmapName() {
String defaultRefmapName = project.getConvention().getPlugin(BasePluginConvention.class).getArchivesBaseName() + "-refmap.json";
String defaultRefmapName = project.getExtensions().getByType(BasePluginExtension.class).getArchivesName().get() + "-refmap.json";
if (project.getRootProject() != project) {
defaultRefmapName = project.getConvention().getPlugin(BasePluginConvention.class).getArchivesBaseName() + "-" + project.getPath().replaceFirst(":", "").replace(':', '_') + "-refmap.json";
}
project.getLogger().info("Could not find refmap definition, will be using default name: " + defaultRefmapName);
return defaultRefmapName;
}
@@ -81,7 +80,7 @@ public class MixinExtensionImpl extends MixinExtensionApiImpl implements MixinEx
protected PatternSet add0(SourceSet sourceSet, Provider<String> refmapName) {
if (!super.getUseLegacyMixinAp().get()) throw new IllegalStateException("You need to set useLegacyMixinAp = true to configure Mixin annotation processor.");
PatternSet pattern = new PatternSet().setIncludes(Collections.singletonList("*.json"));
PatternSet pattern = new PatternSet().setIncludes(Collections.singletonList("**/*.json"));
MixinExtension.setMixinInformationContainer(sourceSet, new MixinExtension.MixinInformationContainer(sourceSet, refmapName, pattern));
isDefault = false;
@@ -92,7 +91,7 @@ public class MixinExtensionImpl extends MixinExtensionApiImpl implements MixinEx
@Override
@NotNull
public Stream<SourceSet> getMixinSourceSetsStream() {
return project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets().stream()
return project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets().stream()
.filter(sourceSet -> MixinExtension.getMixinInformationContainer(sourceSet) != null);
}
@@ -127,7 +126,7 @@ public class MixinExtensionImpl extends MixinExtensionApiImpl implements MixinEx
@Override
public void init() {
if (isDefault) {
project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets().forEach(this::add);
project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets().forEach(this::add);
}
isDefault = false;

View File

@@ -0,0 +1,123 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2021 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.task;
import java.io.IOException;
import java.nio.file.Path;
import javax.inject.Inject;
import org.gradle.api.Action;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.InputFiles;
import org.gradle.jvm.tasks.Jar;
import org.gradle.workers.WorkAction;
import org.gradle.workers.WorkParameters;
import org.gradle.workers.WorkQueue;
import org.gradle.workers.WorkerExecutor;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.util.ZipReprocessorUtil;
public abstract class AbstractRemapJarTask extends Jar {
@InputFile
public abstract RegularFileProperty getInputFile();
@InputFiles
public abstract ConfigurableFileCollection getClasspath();
@Input
public abstract Property<String> getSourceNamespace();
@Input
public abstract Property<String> getTargetNamespace();
@Inject
protected abstract WorkerExecutor getWorkerExecutor();
@Inject
public AbstractRemapJarTask() {
getSourceNamespace().convention(MappingsNamespace.NAMED.toString()).finalizeValueOnRead();
getTargetNamespace().convention(MappingsNamespace.INTERMEDIARY.toString()).finalizeValueOnRead();
}
public final <P extends AbstractRemapParams> void submitWork(Class<? extends AbstractRemapAction<P>> workAction, Action<P> action) {
final WorkQueue workQueue = getWorkerExecutor().noIsolation();
workQueue.submit(workAction, params -> {
params.getInputFile().set(getInputFile());
params.getOutputFile().set(getArchiveFile());
params.getSourceNamespace().set(getSourceNamespace());
params.getTargetNamespace().set(getTargetNamespace());
params.getArchivePreserveFileTimestamps().set(isPreserveFileTimestamps());
params.getArchiveReproducibleFileOrder().set(isReproducibleFileOrder());
action.execute(params);
});
}
public interface AbstractRemapParams extends WorkParameters {
RegularFileProperty getInputFile();
RegularFileProperty getOutputFile();
Property<String> getSourceNamespace();
Property<String> getTargetNamespace();
Property<Boolean> getArchivePreserveFileTimestamps();
Property<Boolean> getArchiveReproducibleFileOrder();
}
public abstract static class AbstractRemapAction<T extends AbstractRemapParams> implements WorkAction<T> {
protected final Path inputFile;
protected final Path outputFile;
@Inject
public AbstractRemapAction() {
inputFile = getParameters().getInputFile().getAsFile().get().toPath();
outputFile = getParameters().getOutputFile().getAsFile().get().toPath();
}
protected void rewriteJar() throws IOException {
final boolean isReproducibleFileOrder = getParameters().getArchiveReproducibleFileOrder().get();
final boolean isPreserveFileTimestamps = getParameters().getArchivePreserveFileTimestamps().get();
if (isReproducibleFileOrder || !isPreserveFileTimestamps) {
ZipReprocessorUtil.reprocessZip(outputFile.toFile(), isReproducibleFileOrder, isPreserveFileTimestamps);
}
}
}
@Deprecated
@InputFile
public RegularFileProperty getInput() {
return getInputFile();
}
}

View File

@@ -31,6 +31,7 @@ import java.util.List;
import java.util.function.Function;
import org.gradle.api.Project;
import org.gradle.api.specs.Spec;
import org.gradle.api.tasks.JavaExec;
import net.fabricmc.loom.configuration.ide.RunConfig;
@@ -44,8 +45,9 @@ public abstract class AbstractRunTask extends JavaExec {
setGroup(Constants.TaskGroup.FABRIC);
this.config = configProvider.apply(getProject());
setClasspath(config.sourceSet.getRuntimeClasspath());
setClasspath(config.sourceSet.getRuntimeClasspath().filter(new LibraryFilter()));
args(config.programArgs);
getMainClass().set(config.mainClass);
}
@Override
@@ -65,11 +67,6 @@ public abstract class AbstractRunTask extends JavaExec {
super.setWorkingDir(dir);
}
@Override
public String getMain() {
return config.mainClass;
}
@Override
public List<String> getJvmArgs() {
List<String> superArgs = super.getJvmArgs();
@@ -77,4 +74,22 @@ public abstract class AbstractRunTask extends JavaExec {
args.addAll(config.vmArgs);
return args;
}
private class LibraryFilter implements Spec<File> {
private List<String> excludedLibraryPaths = null;
@Override
public boolean isSatisfiedBy(File element) {
if (excludedLibraryPaths == null) {
excludedLibraryPaths = config.getExcludedLibraryPaths(getProject());
}
if (excludedLibraryPaths.contains(element.getAbsolutePath())) {
getProject().getLogger().debug("Excluding library {} from {} run config", element.getName(), config.configName);
return false;
}
return true;
}
}
}

View File

@@ -30,7 +30,6 @@ import org.gradle.api.Project;
import org.gradle.api.tasks.TaskAction;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftNativesProvider;
import net.fabricmc.loom.configuration.providers.minecraft.assets.MinecraftAssetsProvider;
public class DownloadAssetsTask extends AbstractLoomTask {
@@ -40,6 +39,5 @@ public class DownloadAssetsTask extends AbstractLoomTask {
LoomGradleExtension extension = getExtension();
MinecraftAssetsProvider.provide(extension.getMinecraftProvider(), project);
MinecraftNativesProvider.provide(project);
}
}

View File

@@ -0,0 +1,50 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2021 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.task;
import java.io.File;
import javax.inject.Inject;
import org.gradle.api.tasks.Sync;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.util.Constants;
public abstract class ExtractNativesTask extends Sync {
@Inject
public ExtractNativesTask() {
// Is there a lazy way to do this for many files? - Doesnt seem there is...
for (File nativeFile : getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_NATIVES).getFiles()) {
from(getProject().zipTree(nativeFile), copySpec -> {
copySpec.exclude("META-INF/**");
});
}
into(LoomGradleExtension.get(getProject()).getFiles().getNativesDirectory(getProject()));
setDescription("Downloads and extracts the minecraft natives");
}
}

View File

@@ -49,6 +49,7 @@ import org.gradle.api.provider.MapProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.TaskAction;
import org.gradle.workers.WorkAction;
import org.gradle.workers.WorkParameters;
@@ -89,6 +90,9 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
@Input
public abstract MapProperty<String, String> getOptions();
@InputFiles
public abstract ConfigurableFileCollection getClasspath();
@Inject
public abstract WorkerExecutor getWorkerExecutor();
@@ -102,6 +106,12 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
Objects.requireNonNull(getDecompilerConstructor(this.decompiler.getClass().getCanonicalName()),
"%s must have a no args constructor".formatted(this.decompiler.getClass().getCanonicalName()));
FileCollection decompilerClasspath = decompiler.getBootstrapClasspath(getProject());
if (decompilerClasspath != null) {
getClasspath().from(decompilerClasspath);
}
getOutputs().upToDateWhen((o) -> false);
getMaxMemory().convention(4096L).finalizeValueOnRead();
getOptions().finalizeValueOnRead();
@@ -179,6 +189,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
spec.forkOptions(forkOptions -> {
forkOptions.setMaxHeapSize("%dm".formatted(getMaxMemory().get()));
forkOptions.systemProperty(WorkerDaemonClientsManagerHelper.MARKER_PROP, jvmMarkerValue);
forkOptions.bootstrapClasspath(getClasspath());
});
});
}

View File

@@ -24,21 +24,13 @@
package net.fabricmc.loom.task;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import com.google.common.base.Preconditions;
import org.gradle.api.Project;
import org.gradle.api.tasks.TaskContainer;
import org.gradle.api.tasks.TaskProvider;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.decompilers.LoomDecompiler;
import net.fabricmc.loom.api.decompilers.architectury.ArchitecturyLoomDecompiler;
import net.fabricmc.loom.configuration.ide.RunConfigSettings;
import net.fabricmc.loom.configuration.ide.SetupIntelijRunConfigs;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.util.Constants;
public final class LoomTasks {
@@ -53,13 +45,13 @@ public final class LoomTasks {
t.getOutputs().upToDateWhen(o -> false);
});
tasks.register("remapJar", RemapJarTask.class, t -> {
t.setDescription("Remaps the built project jar to intermediary mappings.");
t.setGroup(Constants.TaskGroup.FABRIC);
});
RemapTaskConfiguration.setupRemap(project);
tasks.register("downloadAssets", DownloadAssetsTask.class, t -> t.setDescription("Downloads required assets for Fabric."));
tasks.register("remapSourcesJar", RemapSourcesJarTask.class, t -> t.setDescription("Remaps the project sources jar to intermediary names."));
TaskProvider<ExtractNativesTask> extractNatives = tasks.register("extractNatives", ExtractNativesTask.class);
tasks.register("downloadAssets", DownloadAssetsTask.class, t -> {
t.dependsOn(extractNatives);
t.setDescription("Downloads required assets for Fabric.");
});
TaskProvider<ValidateAccessWidenerTask> validateAccessWidener = tasks.register("validateAccessWidener", ValidateAccessWidenerTask.class, t -> {
t.setDescription("Validate all the rules in the access widener against the Minecraft jar");
@@ -146,51 +138,15 @@ public final class LoomTasks {
}
private static void registerDecompileTasks(TaskContainer tasks, Project project) {
LoomGradleExtension extension = LoomGradleExtension.get(project);
project.afterEvaluate(p -> {
MappingsProviderImpl mappingsProvider = extension.getMappingsProvider();
if (mappingsProvider.mappedProvider == null) {
// If this is ever null something has gone badly wrong,
// for some reason for another this afterEvaluate still gets called when something has gone badly
// wrong, returning here seems to produce nicer errors.
return;
}
File mappedJar = mappingsProvider.mappedProvider.getMappedJar();
if (mappingsProvider.hasUnpickDefinitions()) {
File outputJar = mappingsProvider.mappedProvider.getUnpickedJar();
tasks.register("unpickJar", UnpickJarTask.class, unpickJarTask -> {
unpickJarTask.getUnpickDefinitions().set(mappingsProvider.getUnpickDefinitionsFile());
unpickJarTask.getInputJar().set(mappingsProvider.mappedProvider.getMappedJar());
unpickJarTask.getOutputJar().set(outputJar);
});
mappedJar = outputJar;
}
final File inputJar = mappedJar;
extension.getGameDecompilers().finalizeValue();
for (LoomDecompiler decompiler : extension.getGameDecompilers().get()) {
String taskName = "genSourcesWith" + decompiler.name();
// Decompiler will be passed to the constructor of GenerateSourcesTask
tasks.register(taskName, GenerateSourcesTask.class, decompiler).configure(task -> {
task.setDescription("Decompile minecraft using %s.".formatted(decompiler.name()));
task.setGroup(Constants.TaskGroup.FABRIC);
task.getInputJar().set(inputJar);
if (mappingsProvider.hasUnpickDefinitions()) {
task.dependsOn(tasks.named("unpickJar"));
}
task.dependsOn(tasks.named("validateAccessWidener"));
});
}
LoomGradleExtension.get(project).getGameDecompilers().configureEach(decompiler -> {
String taskName = "genSourcesWith" + decompiler.name();
// Decompiler will be passed to the constructor of GenerateSourcesTask
tasks.register(taskName, GenerateSourcesTask.class, decompiler).configure(task -> {
task.setDescription("Decompile minecraft using %s.".formatted(decompiler.name()));
task.setGroup(Constants.TaskGroup.FABRIC);
task.dependsOn(tasks.named("validateAccessWidener"));
});
});
for (ArchitecturyLoomDecompiler decompiler : extension.getArchGameDecompilers().get()) {
String taskName = "genSourcesWith" + decompiler.name();
@@ -206,12 +162,11 @@ public final class LoomTasks {
});
}
tasks.register("genSources", task -> {
task.setDescription("Decompile minecraft using the default decompiler.");
task.setGroup(Constants.TaskGroup.FABRIC);
tasks.register("genSources", task -> {
task.setDescription("Decompile minecraft using the default decompiler.");
task.setGroup(Constants.TaskGroup.FABRIC);
task.dependsOn(project.getTasks().named("genSourcesWithCfr"));
});
task.dependsOn(project.getTasks().named("genSourcesWithCfr"));
});
}
}

View File

@@ -42,7 +42,7 @@ import org.gradle.api.IllegalDependencyNotation;
import org.gradle.api.JavaVersion;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.plugins.JavaPluginConvention;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.options.Option;
@@ -171,12 +171,7 @@ public class MigrateMappingsTask extends AbstractLoomTask {
project.getLogger().lifecycle(":remapping");
Mercury mercury = SourceRemapper.createMercuryWithClassPath(project, false);
final JavaPluginConvention convention = project.getConvention().findPlugin(JavaPluginConvention.class);
final JavaVersion javaVersion = convention != null
?
convention.getSourceCompatibility()
:
JavaVersion.current();
final JavaVersion javaVersion = project.getExtensions().getByType(JavaPluginExtension.class).getSourceCompatibility();
mercury.setSourceCompatibility(javaVersion.toString());
mercury.getClassPath().add(minecraftMappedProvider.getMappedJar().toPath());

View File

@@ -1,7 +1,7 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2016-2021 FabricMC
* Copyright (c) 2021 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -24,443 +24,306 @@
package net.fabricmc.loom.task;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileSystem;
import java.io.Serializable;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
import javax.inject.Inject;
import com.google.common.base.Preconditions;
import dev.architectury.tinyremapper.IMappingProvider;
import dev.architectury.tinyremapper.TinyRemapper;
import dev.architectury.tinyremapper.TinyUtils;
import dev.architectury.tinyremapper.extension.mixin.MixinExtension;
import org.cadixdev.at.AccessTransformSet;
import org.cadixdev.at.io.AccessTransformFormats;
import org.cadixdev.lorenz.MappingSet;
import org.gradle.api.Action;
import org.gradle.api.Project;
import com.google.gson.JsonObject;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.FileCollection;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.SetProperty;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.TaskAction;
import org.gradle.jvm.tasks.Jar;
import org.jetbrains.annotations.ApiStatus;
import org.objectweb.asm.commons.Remapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.accesswidener.AccessWidenerReader;
import net.fabricmc.accesswidener.AccessWidenerRemapper;
import net.fabricmc.accesswidener.AccessWidenerWriter;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.build.JarRemapper;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.build.MixinRefmapHelper;
import net.fabricmc.loom.build.nesting.EmptyNestedJarProvider;
import net.fabricmc.loom.build.nesting.IncludedJarFactory;
import net.fabricmc.loom.build.nesting.JarNester;
import net.fabricmc.loom.build.nesting.MergedNestedJarProvider;
import net.fabricmc.loom.build.nesting.NestedDependencyProvider;
import net.fabricmc.loom.build.nesting.NestedJarPathProvider;
import net.fabricmc.loom.build.nesting.NestedJarProvider;
import net.fabricmc.loom.configuration.JarManifestConfiguration;
import net.fabricmc.loom.configuration.accesswidener.AccessWidenerFile;
import net.fabricmc.loom.configuration.accesswidener.AccessWidenerJarProcessor;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.extension.MixinExtension;
import net.fabricmc.loom.task.service.JarManifestService;
import net.fabricmc.loom.task.service.MappingsService;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.FileSystemUtil;
import net.fabricmc.loom.util.LfWriter;
import net.fabricmc.loom.util.SourceRemapper;
import net.fabricmc.loom.util.TinyRemapperHelper;
import net.fabricmc.loom.util.ZipReprocessorUtil;
import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.loom.util.aw2at.Aw2At;
import net.fabricmc.lorenztiny.TinyMappingsReader;
import net.fabricmc.mappingio.tree.MappingTree;
import net.fabricmc.stitch.util.Pair;
import net.fabricmc.tinyremapper.InputTag;
import net.fabricmc.tinyremapper.OutputConsumerPath;
import net.fabricmc.tinyremapper.TinyRemapper;
import net.fabricmc.tinyremapper.TinyUtils;
public class RemapJarTask extends Jar {
public abstract class RemapJarTask extends AbstractRemapJarTask {
private static final String MANIFEST_PATH = "META-INF/MANIFEST.MF";
private final RegularFileProperty input;
private final Property<Boolean> addNestedDependencies;
private final Property<Boolean> addDefaultNestedDependencies;
private final Property<Boolean> remapAccessWidener;
private final List<Action<TinyRemapper.Builder>> remapOptions = new ArrayList<>();
private final Property<String> fromM;
private final Property<String> toM;
private final SetProperty<String> atAccessWideners;
public JarRemapper jarRemapper;
private FileCollection classpath;
private final Set<Object> nestedPaths = new LinkedHashSet<>();
@InputFiles
public abstract ConfigurableFileCollection getNestedJars();
@Input
public abstract Property<Boolean> getAddNestedDependencies();
@Inject
public RemapJarTask() {
super();
LoomGradleExtension extension = LoomGradleExtension.get(getProject());
input = getProject().getObjects().fileProperty();
addNestedDependencies = getProject().getObjects().property(Boolean.class)
.convention(false);
addDefaultNestedDependencies = getProject().getObjects().property(Boolean.class)
.convention(true);
remapAccessWidener = getProject().getObjects().property(Boolean.class)
.convention(false);
fromM = getProject().getObjects().property(String.class)
.convention("named");
toM = getProject().getObjects().property(String.class)
.convention(SourceRemapper.intermediary(getProject()));
atAccessWideners = getProject().getObjects().setProperty(String.class)
.empty();
if (!extension.getMixin().getUseLegacyMixinAp().get()) {
remapOptions.add(b -> b.extension(new MixinExtension()));
}
getClasspath().from(getProject().getConfigurations().getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME));
getAddNestedDependencies().convention(true).finalizeValueOnRead();
Configuration includeConfiguration = getProject().getConfigurations().getByName(Constants.Configurations.INCLUDE);
getNestedJars().from(new IncludedJarFactory(getProject()).getNestedJars(includeConfiguration));
}
@TaskAction
public void doTask() throws Throwable {
boolean singleRemap = false;
public void run() {
final LoomGradleExtension extension = LoomGradleExtension.get(getProject());
if (jarRemapper == null) {
singleRemap = true;
jarRemapper = new JarRemapper();
}
scheduleRemap(singleRemap || LoomGradleExtension.get(getProject()).isRootProject());
if (singleRemap) {
jarRemapper.remap(getProject());
}
convertAwToAt();
}
public void scheduleRemap(boolean isMainRemapTask) throws Throwable {
Project project = getProject();
LoomGradleExtension extension = LoomGradleExtension.get(getProject());
Path input = this.getInput().getAsFile().get().toPath();
Path output = this.getArchivePath().toPath();
if (!Files.exists(input)) {
throw new FileNotFoundException(input.toString());
}
MappingsProviderImpl mappingsProvider = extension.getMappingsProvider();
String fromM = this.fromM.get();
String toM = this.toM.get();
if (isMainRemapTask) {
jarRemapper.addToClasspath(getRemapClasspath());
jarRemapper.addMappings(TinyRemapperHelper.create((fromM.equals("srg") || toM.equals("srg")) && extension.shouldGenerateSrgTiny() ? mappingsProvider.getMappingsWithSrg() : mappingsProvider.getMappings(), fromM, toM, false));
}
for (File mixinMapFile : extension.getAllMixinMappings()) {
if (mixinMapFile.exists()) {
IMappingProvider provider = TinyUtils.createTinyMappingProvider(mixinMapFile.toPath(), fromM, "intermediary");
jarRemapper.addMappings(!toM.equals("intermediary") ? remapToSrg(extension, provider, "intermediary", toM) : provider);
submitWork(RemapAction.class, params -> {
if (getAddNestedDependencies().get()) {
params.getNestedJars().from(getNestedJars());
}
}
// Add remap options to the jar remapper
jarRemapper.addOptions(this.remapOptions);
params.getJarManifestService().set(JarManifestService.get(getProject()));
params.getRemapClasspath().from(getClasspath());
params.getMappings().add(MappingsService.createDefault(getProject(), getSourceNamespace().get(), getTargetNamespace().get()));
project.getLogger().info(":scheduling remap " + input.getFileName() + " from " + fromM + " to " + toM);
final boolean legacyMixin = extension.getMixin().getUseLegacyMixinAp().get();
params.getUseMixinExtension().set(!legacyMixin);
NestedJarProvider nestedJarProvider = getNestedJarProvider();
nestedJarProvider.prepare(getProject());
jarRemapper.scheduleRemap(input, output)
.supplyAccessWidener((remapData, remapper) -> {
if (getRemapAccessWidener().getOrElse(false) && extension.getAccessWidenerPath().isPresent()) {
AccessWidenerJarProcessor accessWidenerJarProcessor = extension.getJarProcessorManager().getByType(AccessWidenerJarProcessor.class);
byte[] data;
try {
data = accessWidenerJarProcessor.getRemappedAccessWidener(remapper, toM);
} catch (IOException e) {
throw new RuntimeException("Failed to remap access widener", e);
}
AccessWidenerFile awFile = AccessWidenerFile.fromModJar(remapData.input);
Preconditions.checkNotNull(awFile, "Failed to find accessWidener in fabric.mod.json / architectury.common.json: " + remapData.input);
return Pair.of(awFile.name(), data);
}
return null;
})
.complete((data, accessWidener) -> {
if (!Files.exists(output)) {
throw new RuntimeException("Failed to remap " + input + " to " + output + " - file missing!");
}
if (extension.getMixin().getUseLegacyMixinAp().get()) {
if (MixinRefmapHelper.addRefmapName(project, output)) {
project.getLogger().debug("Transformed mixin reference maps in output JAR!");
}
} else if (extension.isForge()) {
throw new RuntimeException("Forge must have useLegacyMixinAp enabled");
}
if (getAddNestedDependencies().getOrElse(false)) {
JarNester.nestJars(nestedJarProvider.provide(), output.toFile(), project.getLogger());
}
if (accessWidener != null) {
try {
ZipUtils.replace(data.output, accessWidener.getLeft(), accessWidener.getRight());
} catch (IOException e) {
throw new UncheckedIOException("Failed to replace access widener in output jar", e);
}
}
if (!extension.isForge()) {
// Add data to the manifest
try {
int count = ZipUtils.transform(data.output, Map.of(MANIFEST_PATH, bytes -> {
var manifest = new Manifest(new ByteArrayInputStream(bytes));
var manifestConfiguration = new JarManifestConfiguration(project);
manifestConfiguration.configure(manifest);
manifest.getMainAttributes().putValue("Fabric-Mapping-Namespace", toM);
ByteArrayOutputStream out = new ByteArrayOutputStream();
manifest.write(out);
return out.toByteArray();
}));
Preconditions.checkState(count > 0, "Did not transform any jar manifest");
} catch (IOException e) {
throw new UncheckedIOException("Failed to transform jar manifest", e);
}
}
if (isReproducibleFileOrder() || !isPreserveFileTimestamps()) {
try {
ZipReprocessorUtil.reprocessZip(output.toFile(), isReproducibleFileOrder(), isPreserveFileTimestamps());
} catch (IOException e) {
throw new RuntimeException("Failed to re-process jar", e);
}
}
});
if (legacyMixin) {
params.getMixinMappings().from(extension.getAllMixinMappings());
setupLegacyMixinRefmapRemapping(params);
}
});
}
private NestedJarProvider getNestedJarProvider() {
if (!LoomGradleExtension.get(getProject()).supportsInclude()) {
return EmptyNestedJarProvider.INSTANCE;
}
private void setupLegacyMixinRefmapRemapping(RemapParams params) {
final LoomGradleExtension extension = LoomGradleExtension.get(getProject());
final MixinExtension mixinExtension = extension.getMixin();
Configuration includeConfiguration = getProject().getConfigurations().getByName(Constants.Configurations.INCLUDE);
final JsonObject fabricModJson = MixinRefmapHelper.readFabricModJson(getInputFile().getAsFile().get());
if (!addDefaultNestedDependencies.getOrElse(true)) {
return new NestedJarPathProvider(nestedPaths);
}
NestedJarProvider baseProvider = NestedDependencyProvider.createNestedDependencyProviderFromConfiguration(getProject(), includeConfiguration);
if (nestedPaths.isEmpty()) {
return baseProvider;
}
return new MergedNestedJarProvider(
baseProvider,
new NestedJarPathProvider(nestedPaths)
);
}
private IMappingProvider remapToSrg(LoomGradleExtension extension, IMappingProvider parent, String from, String to) throws IOException {
MappingTree mappings = (from.equals("srg") || to.equals("srg")) && extension.shouldGenerateSrgTiny() ? extension.getMappingsProvider().getMappingsWithSrg() : extension.getMappingsProvider().getMappings();
return sink -> {
parent.load(new IMappingProvider.MappingAcceptor() {
@Override
public void acceptClass(String srcName, String dstName) {
String srgName = mappings.getClasses()
.stream()
.filter(it -> Objects.equals(it.getName(from), dstName))
.findFirst()
.map(it -> it.getName(to))
.orElse(dstName);
sink.acceptClass(srcName, srgName);
}
@Override
public void acceptMethod(IMappingProvider.Member method, String dstName) {
String srgName = mappings.getClasses()
.stream()
.flatMap(it -> it.getMethods().stream())
.filter(it -> Objects.equals(it.getName(from), dstName))
.findFirst()
.map(it -> it.getName(to))
.orElse(dstName);
sink.acceptMethod(method, srgName);
}
@Override
public void acceptField(IMappingProvider.Member field, String dstName) {
String srgName = mappings.getClasses()
.stream()
.flatMap(it -> it.getFields().stream())
.filter(it -> Objects.equals(it.getName(from), dstName))
.findFirst()
.map(it -> it.getName(to))
.orElse(dstName);
sink.acceptField(field, srgName);
}
@Override
public void acceptMethodArg(IMappingProvider.Member method, int lvIndex, String dstName) {
}
@Override
public void acceptMethodVar(IMappingProvider.Member method, int lvIndex, int startOpIdx, int asmIndex, String dstName) {
}
});
};
}
private Path[] getRemapClasspath() {
FileCollection files = this.classpath;
if (files == null) {
files = getProject().getConfigurations().getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME);
}
return files.getFiles().stream()
.map(File::toPath)
.filter(Files::exists)
.toArray(Path[]::new);
}
private void convertAwToAt() throws IOException {
if (!this.atAccessWideners.isPresent()) {
if (fabricModJson == null) {
getProject().getLogger().warn("Could not find fabric.mod.json file in: " + getInputFile().getAsFile().get().getName());
return;
}
Set<String> atAccessWideners = this.atAccessWideners.get();
final Collection<String> allMixinConfigs = MixinRefmapHelper.getMixinConfigurationFiles(fabricModJson);
if (atAccessWideners.isEmpty()) {
return;
for (SourceSet sourceSet : mixinExtension.getMixinSourceSets()) {
MixinExtension.MixinInformationContainer container = Objects.requireNonNull(
MixinExtension.getMixinInformationContainer(sourceSet)
);
String[] rootPaths = sourceSet.getResources().getSrcDirs().stream()
.map(root -> {
String rootPath = root.getAbsolutePath().replace("\\", "/");
if (rootPath.charAt(rootPath.length() - 1) != '/') {
rootPath += '/';
}
return rootPath;
})
.toArray(String[]::new);
final String refmapName = container.refmapNameProvider().get();
final List<String> mixinConfigs = container.sourceSet().getResources()
.matching(container.mixinConfigPattern())
.getFiles()
.stream()
.map(file -> {
String s = file.getAbsolutePath().replace("\\", "/");
for (String rootPath : rootPaths) {
if (s.startsWith(rootPath)) {
s = s.substring(rootPath.length());
}
}
return s;
})
.filter(allMixinConfigs::contains)
.toList();
params.getMixinData().add(new RemapParams.RefmapData(mixinConfigs, refmapName));
}
}
AccessTransformSet at = AccessTransformSet.create();
File jar = getArchiveFile().get().getAsFile();
public interface RemapParams extends AbstractRemapParams {
ConfigurableFileCollection getNestedJars();
ConfigurableFileCollection getRemapClasspath();
ConfigurableFileCollection getMixinMappings();
ListProperty<Provider<MappingsService>> getMappings();
try (FileSystemUtil.Delegate fileSystem = FileSystemUtil.getJarFileSystem(jar, false)) {
FileSystem fs = fileSystem.get();
Path atPath = fs.getPath(Constants.Forge.ACCESS_TRANSFORMER_PATH);
Property<Boolean> getUseMixinExtension();
if (Files.exists(atPath)) {
throw new FileAlreadyExistsException("Jar " + jar + " already contains an access transformer - cannot convert AWs!");
}
record RefmapData(List<String> mixinConfigs, String refmapName) implements Serializable { }
ListProperty<RefmapData> getMixinData();
for (String aw : atAccessWideners) {
Path awPath = fs.getPath(aw);
Property<JarManifestService> getJarManifestService();
}
if (Files.notExists(awPath)) {
throw new NoSuchFileException("Could not find AW '" + aw + "' to convert into AT!");
public abstract static class RemapAction extends AbstractRemapAction<RemapParams> {
private static final Logger LOGGER = LoggerFactory.getLogger(RemapAction.class);
private TinyRemapper tinyRemapper;
@Override
public void execute() {
try {
LOGGER.info("Remapping {} to {}", inputFile, outputFile);
tinyRemapper = createTinyRemapper();
remap();
remapAccessWidener();
addRefmaps();
addNestedJars();
modifyJarManifest();
rewriteJar();
tinyRemapper.finish();
tinyRemapper = null;
LOGGER.debug("Finished remapping {}", inputFile);
} catch (Exception e) {
try {
Files.deleteIfExists(outputFile);
} catch (IOException ex) {
LOGGER.error("Failed to delete output file", ex);
}
try (BufferedReader reader = Files.newBufferedReader(awPath, StandardCharsets.UTF_8)) {
at.merge(Aw2At.toAccessTransformSet(reader));
}
Files.delete(awPath);
}
LoomGradleExtension extension = LoomGradleExtension.get(getProject());
MappingTree mappings = (fromM.get().equals("srg") || toM.get().equals("srg")) && extension.shouldGenerateSrgTiny() ? extension.getMappingsProvider().getMappingsWithSrg() : extension.getMappingsProvider().getMappings();
try (TinyMappingsReader reader = new TinyMappingsReader(mappings, fromM.get(), toM.get())) {
MappingSet mappingSet = reader.read();
at = at.remap(mappingSet);
}
try (Writer writer = new LfWriter(Files.newBufferedWriter(atPath))) {
AccessTransformFormats.FML.write(writer, at);
throw new RuntimeException("Failed to remap", e);
}
}
}
@InputFile
public RegularFileProperty getInput() {
return input;
}
private void remap() throws IOException {
final InputTag inputTag = tinyRemapper.createInputTag();
@Input
public Property<Boolean> getAddNestedDependencies() {
return addNestedDependencies;
}
tinyRemapper.readInputsAsync(inputTag, inputFile);
@Input
public Property<Boolean> getAddDefaultNestedDependencies() {
return addDefaultNestedDependencies;
}
@Input
public Property<Boolean> getRemapAccessWidener() {
return remapAccessWidener;
}
/**
* Gets the jar paths to the access wideners that will be converted to ATs for Forge runtime.
* If you specify multiple files, they will be merged into one.
*
* <p>The specified files will be converted and removed from the final jar.
*
* @return the property containing access widener paths in the final jar
*/
@Input
public SetProperty<String> getAtAccessWideners() {
return atAccessWideners;
}
public void remapOptions(Action<TinyRemapper.Builder> action) {
this.remapOptions.add(action);
}
public RemapJarTask classpath(FileCollection collection) {
if (this.classpath == null) {
this.classpath = collection;
} else {
this.classpath = this.classpath.plus(collection);
try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(outputFile).build()) {
outputConsumer.addNonClassFiles(inputFile);
tinyRemapper.apply(outputConsumer, inputTag);
}
}
return this;
}
private void remapAccessWidener() throws IOException {
final AccessWidenerFile accessWidenerFile = AccessWidenerFile.fromModJar(inputFile);
@ApiStatus.Experimental
// This only allows mod jars, proceed with care when trying to pass in configurations with projects, or something that depends on a task.
public RemapJarTask include(Object... paths) {
Collections.addAll(nestedPaths, paths);
this.addNestedDependencies.set(true);
if (accessWidenerFile == null) {
return;
}
return this;
}
byte[] remapped = remapAccessWidener(accessWidenerFile.content(), tinyRemapper.getEnvironment().getRemapper(), MappingsNamespace.INTERMEDIARY.toString());
@Input
public Property<String> getFromM() {
return fromM;
}
// Finally, replace the output with the remaped aw
ZipUtils.replace(outputFile, accessWidenerFile.path(), remapped);
}
@Input
public Property<String> getToM() {
return toM;
private static byte[] remapAccessWidener(byte[] input, Remapper asmRemapper, String targetNamespace) {
int version = AccessWidenerReader.readVersion(input);
AccessWidenerWriter writer = new AccessWidenerWriter(version);
AccessWidenerRemapper remapper = new AccessWidenerRemapper(
writer,
asmRemapper,
MappingsNamespace.NAMED.toString(),
targetNamespace
);
AccessWidenerReader reader = new AccessWidenerReader(remapper);
reader.read(input);
return writer.write();
}
private void addNestedJars() {
FileCollection nestedJars = getParameters().getNestedJars();
if (nestedJars.isEmpty()) {
LOGGER.info("No jars to nest");
return;
}
JarNester.nestJars(nestedJars.getFiles(), outputFile.toFile(), LOGGER);
}
private void modifyJarManifest() throws IOException {
int count = ZipUtils.transform(outputFile, Map.of(MANIFEST_PATH, bytes -> {
var manifest = new Manifest(new ByteArrayInputStream(bytes));
getParameters().getJarManifestService().get().apply(manifest);
manifest.getMainAttributes().putValue("Fabric-Mapping-Namespace", getParameters().getTargetNamespace().get());
ByteArrayOutputStream out = new ByteArrayOutputStream();
manifest.write(out);
return out.toByteArray();
}));
Preconditions.checkState(count > 0, "Did not transform any jar manifest");
}
private void addRefmaps() throws IOException {
if (getParameters().getUseMixinExtension().get()) {
return;
}
for (RemapParams.RefmapData refmapData : getParameters().getMixinData().get()) {
int transformed = ZipUtils.transformJson(JsonObject.class, outputFile, refmapData.mixinConfigs().stream().collect(Collectors.toMap(s -> s, s -> json -> {
if (!json.has("refmap")) {
json.addProperty("refmap", refmapData.refmapName());
}
return json;
})));
}
}
private TinyRemapper createTinyRemapper() {
TinyRemapper.Builder builder = TinyRemapper.newRemapper();
for (Provider<MappingsService> provider : getParameters().getMappings().get()) {
builder.withMappings(provider.get().getMappingsProvider());
}
for (File mixinMapping : getParameters().getMixinMappings()) {
builder.withMappings(TinyUtils.createTinyMappingProvider(mixinMapping.toPath(), getParameters().getSourceNamespace().get(), getParameters().getTargetNamespace().get()));
}
if (getParameters().getUseMixinExtension().get()) {
builder.extension(new net.fabricmc.tinyremapper.extension.mixin.MixinExtension());
}
TinyRemapper remapper = builder.build();
// Apply classpath
for (File file : getParameters().getRemapClasspath()) {
remapper.readClassPathAsync(file.toPath());
}
return remapper;
}
}
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2016-2021 FabricMC
* Copyright (c) 2021 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -24,82 +24,65 @@
package net.fabricmc.loom.task;
import org.gradle.api.file.RegularFileProperty;
import java.io.IOException;
import java.nio.file.Files;
import javax.inject.Inject;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.util.SourceRemapper;
public class RemapSourcesJarTask extends AbstractLoomTask {
private final RegularFileProperty input = getProject().getObjects().fileProperty();
private final RegularFileProperty output = getProject().getObjects().fileProperty().convention(input);
private final Property<String> sourceNamespace;
private final Property<String> targetNamespace;
private SourceRemapper sourceRemapper = null;
private final Property<Boolean> preserveFileTimestamps = getProject().getObjects().property(Boolean.class).convention(true);
private final Property<Boolean> reproducibleFileOrder = getProject().getObjects().property(Boolean.class).convention(false);
import net.fabricmc.loom.task.service.MappingsService;
import net.fabricmc.loom.task.service.SourceRemapperService;
public abstract class RemapSourcesJarTask extends AbstractRemapJarTask {
@Inject
public RemapSourcesJarTask() {
this.sourceNamespace = getProject().getObjects().property(String.class).convention("named");
this.targetNamespace = getProject().getObjects().property(String.class).convention(SourceRemapper.intermediary(getProject()));
super();
getClasspath().from(getProject().getConfigurations().getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME));
}
@TaskAction
public void remap() throws Exception {
if (sourceRemapper == null) {
if (sourceNamespace.get().equals(targetNamespace.get())) {
SourceRemapper.remapSources(getProject(), getInput().get().getAsFile(), getOutput().get().getAsFile(),
targetNamespace.get().equals(MappingsNamespace.NAMED.toString()) ? SourceRemapper.intermediary(getProject()) : "named", targetNamespace.get(), reproducibleFileOrder.get(), preserveFileTimestamps.get());
} else {
SourceRemapper.remapSources(getProject(), getInput().get().getAsFile(), getOutput().get().getAsFile(), sourceNamespace.get(), targetNamespace.get(), reproducibleFileOrder.get(), preserveFileTimestamps.get());
public void run() {
submitWork(RemapSourcesAction.class, params -> {
params.getSourcesRemapperService().set(SourceRemapperService.create(getProject(), MappingsService.createDefault(getProject(), getSourceNamespace().get(), getTargetNamespace().get()), getClasspath()));
});
}
public interface RemapSourcesParams extends AbstractRemapParams {
Property<SourceRemapperService> getSourcesRemapperService();
}
public abstract static class RemapSourcesAction extends AbstractRemapAction<RemapSourcesParams> {
private static final Logger LOGGER = LoggerFactory.getLogger(RemapSourcesAction.class);
private final SourceRemapperService sourceRemapperService;
public RemapSourcesAction() {
super();
sourceRemapperService = getParameters().getSourcesRemapperService().get();
}
@Override
public void execute() {
try {
sourceRemapperService.remapSourcesJar(inputFile, outputFile);
rewriteJar();
} catch (Exception e) {
try {
Files.deleteIfExists(outputFile);
} catch (IOException ex) {
LOGGER.error("Failed to delete output file", ex);
}
throw new RuntimeException("Failed to remap sources", e);
}
} else {
sourceRemapper.scheduleRemapSources(input.get().getAsFile(), output.get().getAsFile(), reproducibleFileOrder.get(), preserveFileTimestamps.get());
}
}
@Internal
public SourceRemapper getSourceRemapper() {
return sourceRemapper;
}
public RemapSourcesJarTask setSourceRemapper(SourceRemapper sourceRemapper) {
this.sourceRemapper = sourceRemapper;
return this;
}
@InputFile
public RegularFileProperty getInput() {
return input;
}
@OutputFile
public RegularFileProperty getOutput() {
return output;
}
@Input
public Property<String> getSourceNamespace() {
return sourceNamespace;
}
@Input
public Property<String> getTargetNamespace() {
return targetNamespace;
}
@Input
public Property<Boolean> getPreserveFileTimestamps() {
return preserveFileTimestamps;
}
@Input
public Property<Boolean> getReproducibleFileOrder() {
return reproducibleFileOrder;
}
}

View File

@@ -0,0 +1,138 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2016-2021 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.task;
import java.io.File;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.plugins.BasePlugin;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.TaskContainer;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.api.tasks.bundling.AbstractArchiveTask;
import org.gradle.api.tasks.bundling.Jar;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.util.Constants;
public class RemapTaskConfiguration {
public static final String REMAP_JAR_TASK_NAME = "remapJar";
public static final String REMAP_SOURCES_JAR_TASK_NAME = "remapSourcesJar";
public static void setupRemap(Project project) {
final TaskContainer tasks = project.getTasks();
final LoomGradleExtension extension = LoomGradleExtension.get(project);
if (!extension.getRemapArchives().get()) {
extension.getUnmappedModCollection().from(project.getTasks().getByName(JavaPlugin.JAR_TASK_NAME));
return;
}
// Register the default remap jar task
TaskProvider<RemapJarTask> remapJarTaskProvider = tasks.register(REMAP_JAR_TASK_NAME, RemapJarTask.class, task -> {
final AbstractArchiveTask jarTask = tasks.named(JavaPlugin.JAR_TASK_NAME, AbstractArchiveTask.class).get();
// Basic task setup
task.dependsOn(jarTask);
task.setDescription("Remaps the built project jar to intermediary mappings.");
task.setGroup(Constants.TaskGroup.FABRIC);
project.getArtifacts().add(JavaPlugin.API_ELEMENTS_CONFIGURATION_NAME, task);
project.getArtifacts().add(JavaPlugin.RUNTIME_ELEMENTS_CONFIGURATION_NAME, task);
// Setup the input file and the nested deps
task.getInputFile().convention(jarTask.getArchiveFile());
task.dependsOn(tasks.named(JavaPlugin.JAR_TASK_NAME));
});
// Configure the default jar task
tasks.named(JavaPlugin.JAR_TASK_NAME, AbstractArchiveTask.class).configure(task -> {
task.getArchiveClassifier().convention("dev");
task.getDestinationDirectory().set(new File(project.getBuildDir(), "devlibs"));
});
tasks.named(BasePlugin.ASSEMBLE_TASK_NAME).configure(task -> task.dependsOn(remapJarTaskProvider));
trySetupSourceRemapping(project);
if (extension.getSetupRemappedVariants().get()) {
// Remove -dev jars from the default jar task
for (String configurationName : new String[] { JavaPlugin.API_ELEMENTS_CONFIGURATION_NAME, JavaPlugin.RUNTIME_ELEMENTS_CONFIGURATION_NAME }) {
Configuration configuration = project.getConfigurations().getByName(configurationName);
configuration.getArtifacts().removeIf(artifact -> {
Task jarTask = project.getTasks().getByName(JavaPlugin.JAR_TASK_NAME);
// if the artifact is a -dev jar and "builtBy jar"
return "dev".equals(artifact.getClassifier()) && artifact.getBuildDependencies().getDependencies(null).contains(jarTask);
});
}
}
}
private static void trySetupSourceRemapping(Project project) {
final TaskContainer tasks = project.getTasks();
final LoomGradleExtension extension = LoomGradleExtension.get(project);
TaskProvider<RemapSourcesJarTask> remapSourcesTask = tasks.register(REMAP_SOURCES_JAR_TASK_NAME, RemapSourcesJarTask.class, task -> {
task.setDescription("Remaps the default sources jar to intermediary mappings.");
task.setGroup(Constants.TaskGroup.FABRIC);
final JavaPluginExtension javaExtension = project.getExtensions().getByType(JavaPluginExtension.class);
final String sourcesJarTaskName = javaExtension.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getSourcesJarTaskName();
final Task sourcesTask = project.getTasks().findByName(sourcesJarTaskName);
if (sourcesTask == null) {
project.getLogger().info("{} task was not found, not remapping sources", sourcesJarTaskName);
task.setEnabled(false);
return;
}
if (!(sourcesTask instanceof Jar sourcesJarTask)) {
project.getLogger().info("{} task is not a Jar task, not remapping sources", sourcesJarTaskName);
task.setEnabled(false);
return;
}
sourcesJarTask.getArchiveClassifier().convention("dev-sources");
sourcesJarTask.getDestinationDirectory().set(new File(project.getBuildDir(), "devlibs"));
task.getArchiveClassifier().convention("sources");
task.dependsOn(sourcesJarTask);
task.getInputFile().convention(sourcesJarTask.getArchiveFile());
if (extension.getSetupRemappedVariants().get()) {
project.getArtifacts().add(JavaPlugin.SOURCES_ELEMENTS_CONFIGURATION_NAME, task);
// Remove the dev sources artifact
Configuration configuration = project.getConfigurations().getByName(JavaPlugin.SOURCES_ELEMENTS_CONFIGURATION_NAME);
configuration.getArtifacts().removeIf(a -> a.getFile().equals(sourcesJarTask.getArchiveFile().get().getAsFile()));
}
});
tasks.named(BasePlugin.ASSEMBLE_TASK_NAME).configure(task -> task.dependsOn(remapSourcesTask));
}
}

View File

@@ -59,6 +59,9 @@ public abstract class ValidateAccessWidenerTask extends DefaultTask {
getAccessWidener().convention(extension.getAccessWidenerPath()).finalizeValueOnRead();
getTargetJar().convention(getProject().getObjects().fileProperty().fileValue(extension.getMinecraftMappedProvider().getMappedJar())).finalizeValueOnRead();
// Ignore outputs for up-to-date checks as there aren't any (so only inputs are checked)
getOutputs().upToDateWhen(task -> true);
}
@TaskAction

View File

@@ -0,0 +1,123 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2021 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.task.service;
import java.io.Serializable;
import java.util.Optional;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.services.BuildService;
import org.gradle.api.services.BuildServiceParameters;
import org.gradle.util.GradleVersion;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.tinyremapper.TinyRemapper;
public abstract class JarManifestService implements BuildService<JarManifestService.Params> {
interface Params extends BuildServiceParameters {
Property<String> getGradleVersion();
Property<String> getLoomVersion();
Property<String> getMCEVersion();
Property<String> getMinecraftVersion();
Property<String> getTinyRemapperVersion();
Property<String> getFabricLoaderVersion();
Property<MixinVersion> getMixinVersion();
}
public static Provider<JarManifestService> get(Project project) {
return project.getGradle().getSharedServices().registerIfAbsent("LoomJarManifestService:" + project.getName(), JarManifestService.class, spec -> {
spec.parameters(params -> {
LoomGradleExtension extension = LoomGradleExtension.get(project);
Optional<String> tinyRemapperVersion = Optional.ofNullable(TinyRemapper.class.getPackage().getImplementationVersion());
params.getGradleVersion().set(GradleVersion.current().getVersion());
params.getLoomVersion().set(LoomGradlePlugin.LOOM_VERSION);
params.getMCEVersion().set(Constants.Dependencies.Versions.MIXIN_COMPILE_EXTENSIONS);
params.getMinecraftVersion().set(extension.getMinecraftProvider().minecraftVersion());
params.getTinyRemapperVersion().set(tinyRemapperVersion.orElse("unknown"));
params.getFabricLoaderVersion().set(getLoaderVersion(project).orElse("unknown"));
params.getMixinVersion().set(getMixinVersion(project).orElse(new MixinVersion("unknown", "unknown")));
});
});
}
public void apply(Manifest manifest) {
// Don't set when running the reproducible build tests as it will break them when anything updates
if (Boolean.getBoolean("loom.test.reproducible")) {
return;
}
Attributes attributes = manifest.getMainAttributes();
Params p = getParameters();
attributes.putValue("Fabric-Gradle-Version", p.getGradleVersion().get());
attributes.putValue("Fabric-Loom-Version", p.getLoomVersion().get());
attributes.putValue("Fabric-Mixin-Compile-Extensions-Version", p.getMCEVersion().get());
attributes.putValue("Fabric-Minecraft-Version", p.getMinecraftVersion().get());
attributes.putValue("Fabric-Tiny-Remapper-Version", p.getTinyRemapperVersion().get());
attributes.putValue("Fabric-Loader-Version", p.getFabricLoaderVersion().get());
// This can be overridden by mods if required
if (!attributes.containsKey("Fabric-Mixin-Version")) {
attributes.putValue("Fabric-Mixin-Version", p.getMixinVersion().get().version());
attributes.putValue("Fabric-Mixin-Group", p.getMixinVersion().get().group());
}
}
private static Optional<String> getLoaderVersion(Project project) {
LoomGradleExtension extension = LoomGradleExtension.get(project);
if (extension.getInstallerData() == null) {
project.getLogger().warn("Could not determine fabric loader version for jar manifest");
return Optional.empty();
}
return Optional.of(extension.getInstallerData().version());
}
private record MixinVersion(String group, String version) implements Serializable { }
private static Optional<MixinVersion> getMixinVersion(Project project) {
// Not super ideal that this uses the mod compile classpath, should prob look into making this not a thing at somepoint
Optional<Dependency> dependency = project.getConfigurations().getByName(Constants.Configurations.LOADER_DEPENDENCIES)
.getDependencies()
.stream()
.filter(dep -> "sponge-mixin".equals(dep.getName()))
.findFirst();
if (dependency.isEmpty()) {
project.getLogger().warn("Could not determine Mixin version for jar manifest");
}
return dependency.map(d -> new MixinVersion(d.getGroup(), d.getVersion()));
}
}

View File

@@ -0,0 +1,118 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2021 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.task.service;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import org.gradle.api.Project;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.services.BuildService;
import org.gradle.api.services.BuildServiceParameters;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.util.TinyRemapperHelper;
import net.fabricmc.mappingio.MappingReader;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
import net.fabricmc.tinyremapper.IMappingProvider;
public abstract class MappingsService implements BuildService<MappingsService.Params>, AutoCloseable {
interface Params extends BuildServiceParameters {
RegularFileProperty getMappingsFile();
Property<String> getFromNamespace();
Property<String> getToNamespace();
Property<Boolean> getRemapLocals();
}
public static synchronized Provider<MappingsService> create(Project project, String name, File mappingsFile, String from, String to, boolean remapLocals) {
return project.getGradle().getSharedServices().registerIfAbsent(name, MappingsService.class, spec -> {
spec.parameters(params -> {
params.getMappingsFile().set(mappingsFile);
params.getFromNamespace().set(from);
params.getToNamespace().set(to);
params.getRemapLocals().set(remapLocals);
});
});
}
public static Provider<MappingsService> createDefault(Project project, String from, String to) {
final MappingsProviderImpl mappingsProvider = LoomGradleExtension.get(project).getMappingsProvider();
final String name = mappingsProvider.getBuildServiceName("mappingsProvider", from, to);
return MappingsService.create(project, name, mappingsProvider.tinyMappings.toFile(), from, to, false);
}
private IMappingProvider mappingProvider = null;
private MemoryMappingTree memoryMappingTree = null;
public synchronized IMappingProvider getMappingsProvider() {
if (mappingProvider == null) {
try {
mappingProvider = TinyRemapperHelper.create(
getParameters().getMappingsFile().get().getAsFile().toPath(),
getParameters().getFromNamespace().get(),
getParameters().getToNamespace().get(),
getParameters().getRemapLocals().get()
);
} catch (IOException e) {
throw new UncheckedIOException("Failed to read mappings from: " + getParameters().getMappingsFile().get().getAsFile().getAbsolutePath(), e);
}
}
return mappingProvider;
}
public synchronized MemoryMappingTree getMemoryMappingTree() {
if (memoryMappingTree == null) {
memoryMappingTree = new MemoryMappingTree();
try {
MappingReader.read(getParameters().getMappingsFile().get().getAsFile().toPath(), memoryMappingTree);
} catch (IOException e) {
throw new UncheckedIOException("Failed to read mappings from: " + getParameters().getMappingsFile().get().getAsFile().getAbsolutePath(), e);
}
}
return memoryMappingTree;
}
public String getFromNamespace() {
return getParameters().getFromNamespace().get();
}
public String getToNamespace() {
return getParameters().getToNamespace().get();
}
@Override
public void close() {
mappingProvider = null;
}
}

View File

@@ -0,0 +1,133 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2021 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.task.service;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.cadixdev.lorenz.MappingSet;
import org.cadixdev.mercury.Mercury;
import org.cadixdev.mercury.remapper.MercuryRemapper;
import org.gradle.api.Project;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.FileCollection;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.services.BuildService;
import org.gradle.api.services.BuildServiceParameters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.loom.util.DeletingFileVisitor;
import net.fabricmc.loom.util.FileSystemUtil;
import net.fabricmc.loom.util.SourceRemapper;
import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.lorenztiny.TinyMappingsReader;
public abstract class SourceRemapperService implements BuildService<SourceRemapperService.Params>, AutoCloseable {
public interface Params extends BuildServiceParameters {
Property<Provider<MappingsService>> getMappings();
ConfigurableFileCollection getClasspath();
}
public static synchronized Provider<SourceRemapperService> create(Project project, Provider<MappingsService> mappings, FileCollection classpath) {
// TODO may need a better name, im not too sure
return project.getGradle().getSharedServices().registerIfAbsent("sourceremapper", SourceRemapperService.class, spec ->
spec.parameters(params -> {
params.getMappings().set(mappings);
params.getClasspath().from(classpath);
}
));
}
private static final Logger LOGGER = LoggerFactory.getLogger(SourceRemapperService.class);
private Mercury mercury;
public void remapSourcesJar(Path source, Path destination) throws IOException {
if (source.equals(destination)) {
throw new UnsupportedOperationException("Cannot remap in place");
}
Path srcPath = source;
boolean isSrcTmp = false;
// Create a temp directory with all of the sources
if (!Files.isDirectory(source)) {
isSrcTmp = true;
srcPath = Files.createTempDirectory("fabric-loom-src");
ZipUtils.unpackAll(source, srcPath);
}
if (!Files.isDirectory(destination) && Files.exists(destination)) {
Files.delete(destination);
}
try (FileSystemUtil.Delegate dstFs = Files.isDirectory(destination) ? null : FileSystemUtil.getJarFileSystem(destination, true)) {
Path dstPath = dstFs != null ? dstFs.get().getPath("/") : destination;
doRemap(srcPath, dstPath, source);
SourceRemapper.copyNonJavaFiles(srcPath, dstPath, LOGGER, source);
} finally {
if (isSrcTmp) {
Files.walkFileTree(srcPath, new DeletingFileVisitor());
}
}
}
private synchronized void doRemap(Path srcPath, Path dstPath, Path source) throws IOException {
if (mercury == null) {
mercury = new Mercury();
mercury.setGracefulClasspathChecks(true);
mercury.getProcessors().add(MercuryRemapper.create(getMappings()));
getParameters().getClasspath().forEach(file -> mercury.getClassPath().add(file.toPath()));
}
try {
// Not thread safe!!
mercury.rewrite(srcPath, dstPath);
} catch (Exception e) {
LOGGER.warn("Could not remap " + source + " fully!", e);
}
}
private MappingSet getMappings() throws IOException {
return new TinyMappingsReader(mappingsService().getMemoryMappingTree(), mappingsService().getFromNamespace(), mappingsService().getToNamespace()).read();
}
private MappingsService mappingsService() {
return getParameters().getMappings().get().get();
}
@Override
public void close() throws Exception {
mercury = null;
// This is required (:
System.gc();
}
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2016-2021 FabricMC
* Copyright (c) 2021 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -22,19 +22,26 @@
* SOFTWARE.
*/
package net.fabricmc.loom.build.nesting;
package net.fabricmc.loom.util;
import java.io.File;
import java.util.Collection;
public class Architecture {
public static final Architecture CURRENT = new Architecture(System.getProperty("os.arch"));
import org.gradle.api.Project;
import org.jetbrains.annotations.ApiStatus;
private final String name;
@ApiStatus.Internal
public interface NestedJarProvider {
// provide all the files to be included, they should already be resolved but can be transformed here
Collection<File> provide();
public Architecture(String name) {
this.name = name;
}
// Setup the files ready to be provided
default void prepare(Project project) { }
public boolean is64Bit() {
return name.contains("64") || name.startsWith("armv8");
}
public boolean isArm() {
return name.startsWith("arm") || name.startsWith("aarch64");
}
public String getName() {
return name;
}
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2016-2020 FabricMC
* Copyright (c) 2021 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -22,17 +22,15 @@
* SOFTWARE.
*/
package net.fabricmc.loom.task;
package net.fabricmc.loom.util;
import org.gradle.api.tasks.Internal;
import groovy.lang.Closure;
import org.gradle.api.Action;
import net.fabricmc.loom.util.SourceRemapper;
public class RemapAllSourcesTask extends AbstractLoomTask {
public SourceRemapper sourceRemapper;
@Internal
public SourceRemapper getSourceRemapper() {
return sourceRemapper;
public record ClosureAction<T>(Closure closure) implements Action<T> {
@Override
public void execute(T t) {
closure.setDelegate(t);
closure.call(t);
}
}

View File

@@ -41,8 +41,6 @@ public class Constants {
public static final String EXPERIMENTAL_VERSIONS = "https://maven.fabricmc.net/net/minecraft/experimental_versions.json";
public static final String FABRIC_REPOSITORY = "https://maven.fabricmc.net/";
public static final String SYSTEM_ARCH = System.getProperty("os.arch").equals("64") ? "64" : "32";
public static final int ASM_VERSION = Opcodes.ASM9;
public static final List<RemappedConfigurationEntry> MOD_COMPILE_ENTRIES = ImmutableList.of(
@@ -65,8 +63,13 @@ public class Constants {
public static final String MOD_COMPILE_CLASSPATH_MAPPED = "modCompileClasspathMapped";
public static final String INCLUDE = "include";
public static final String MINECRAFT = "minecraft";
/**
* The server specific configuration will be empty when using a legacy (pre 21w38a server jar)
* find the client only dependencies on the "minecraftLibraries" config.
*/
public static final String MINECRAFT_SERVER_DEPENDENCIES = "minecraftServerLibraries";
public static final String MINECRAFT_DEPENDENCIES = "minecraftLibraries";
public static final String MINECRAFT_REMAP_CLASSPATH = "minecraftRemapClasspath";
public static final String MINECRAFT_NATIVES = "minecraftNatives";
public static final String MINECRAFT_NAMED = "minecraftNamed";
public static final String MAPPINGS = "mappings";
public static final String MAPPINGS_FINAL = "mappingsFinal";
@@ -118,6 +121,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 NATIVE_SUPPORT = "net.fabricmc:fabric-loom-native-support:";
public static final String JAVAX_ANNOTATIONS = "com.google.code.findbugs:jsr305:"; // I hate that I have to add these.
public static final String FORGE_RUNTIME = "dev.architectury:architectury-loom-runtime:";
public static final String ACCESS_TRANSFORMERS = "net.minecraftforge:accesstransformers:";
@@ -133,6 +137,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 = "23.0.0";
public static final String NATIVE_SUPPORT_VERSION = "1.0.1";
public static final String JAVAX_ANNOTATIONS = "3.0.2";
public static final String FORGE_RUNTIME = "1.1.3";
public static final String ACCESS_TRANSFORMERS = "3.0.1";

View File

@@ -28,9 +28,12 @@ import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem;
import java.nio.file.FileSystemAlreadyExistsException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.Collections;
import java.util.Map;
@@ -38,6 +41,20 @@ import java.util.function.Supplier;
public final class FileSystemUtil {
public record Delegate(FileSystem fs, boolean owner) implements AutoCloseable, Supplier<FileSystem> {
public byte[] readAllBytes(String path) throws IOException {
Path fsPath = get().getPath(path);
if (Files.exists(fsPath)) {
return Files.readAllBytes(fsPath);
} else {
throw new NoSuchFileException(fsPath.toString());
}
}
public String readString(String path) throws IOException {
return new String(readAllBytes(path), StandardCharsets.UTF_8);
}
@Override
public void close() throws IOException {
if (owner) {
@@ -65,6 +82,10 @@ public final class FileSystemUtil {
return getJarFileSystem(path.toUri(), create);
}
public static Delegate getJarFileSystem(Path path) throws IOException {
return getJarFileSystem(path, false);
}
public static Delegate getJarFileSystem(URI uri, boolean create) throws IOException {
URI jarUri;

View File

@@ -25,18 +25,12 @@
package net.fabricmc.loom.util;
import java.io.File;
import java.io.IOException;
import java.util.zip.ZipFile;
public final class ModUtils {
private ModUtils() {
}
public static boolean isMod(File input) {
try (ZipFile zipFile = new ZipFile(input)) {
return zipFile.getEntry("fabric.mod.json") != null;
} catch (IOException e) {
return false;
}
return ZipUtils.contains(input.toPath(), "fabric.mod.json");
}
}

View File

@@ -30,23 +30,21 @@ import java.net.StandardProtocolFamily;
import java.nio.channels.ServerSocketChannel;
public class OperatingSystem {
public static String getOS() {
public static final String WINDOWS = "windows";
public static final String MAC_OS = "osx";
public static final String LINUX = "linux";
public static final String CURRENT_OS = getOS();
private static String getOS() {
String osName = System.getProperty("os.name").toLowerCase();
if (osName.contains("win")) {
return "windows";
return WINDOWS;
} else if (osName.contains("mac")) {
return "osx";
return MAC_OS;
} else {
return "linux";
}
}
public static String getArch() {
if (is64Bit()) {
return "64";
} else {
return "32";
return LINUX;
}
}
@@ -54,10 +52,6 @@ public class OperatingSystem {
return System.getProperty("sun.arch.data.model").contains("64");
}
public static boolean isWindows() {
return getOS().equals("windows");
}
public static boolean isCIBuild() {
String loomProperty = System.getProperty("fabric.loom.ci");

View File

@@ -37,6 +37,7 @@ import org.cadixdev.lorenz.MappingSet;
import org.cadixdev.mercury.Mercury;
import org.cadixdev.mercury.remapper.MercuryRemapper;
import org.gradle.api.Project;
import org.slf4j.Logger;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.RemappedConfigurationEntry;
@@ -152,7 +153,7 @@ public class SourceRemapper {
project.getLogger().warn("Could not remap " + source.getName() + " fully!", e);
}
copyNonJavaFiles(srcPath, dstPath, project, source);
copyNonJavaFiles(srcPath, dstPath, project.getLogger(), source.toPath());
if (dstFs != null) {
dstFs.close();
@@ -231,7 +232,7 @@ public class SourceRemapper {
return mercury;
}
private static void copyNonJavaFiles(Path from, Path to, Project project, File source) throws IOException {
public static void copyNonJavaFiles(Path from, Path to, Logger logger, Path source) throws IOException {
Files.walk(from).forEach(path -> {
Path targetPath = to.resolve(from.relativize(path).toString());
@@ -239,7 +240,7 @@ public class SourceRemapper {
try {
Files.copy(path, targetPath);
} catch (IOException e) {
project.getLogger().warn("Could not copy non-java sources '" + source.getName() + "' fully!", e);
logger.warn("Could not copy non-java sources '" + source + "' fully!", e);
}
}
});

View File

@@ -44,6 +44,7 @@ import org.gradle.api.Project;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.mappingio.MappingReader;
import net.fabricmc.mappingio.tree.MappingTree;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
@@ -138,6 +139,12 @@ public final class TinyRemapperHelper {
return new IMappingProvider.Member(className, memberName, descriptor);
}
public static IMappingProvider create(Path mappings, String from, String to, boolean remapLocalVariables) throws IOException {
MemoryMappingTree mappingTree = new MemoryMappingTree();
MappingReader.read(mappings, mappingTree);
return create(mappingTree, from, to, remapLocalVariables);
}
public static IMappingProvider create(MappingTree mappings, String from, String to, boolean remapLocalVariables) {
return (acceptor) -> {
for (MappingTree.ClassMapping classDef : mappings.getClasses()) {

View File

@@ -83,13 +83,7 @@ public class ZipUtils {
public static byte[] unpack(Path zip, String path) throws IOException {
try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(zip, false)) {
Path fsPath = fs.get().getPath(path);
if (Files.exists(fsPath)) {
return Files.readAllBytes(fsPath);
} else {
throw new NoSuchFileException(fsPath.toString());
}
return fs.readAllBytes(path);
}
}

View File

@@ -28,7 +28,7 @@ import org.gradle.util.GradleVersion
class LoomTestConstants {
public final static String DEFAULT_GRADLE = GradleVersion.current().getVersion()
public final static String PRE_RELEASE_GRADLE = "7.4-20211203231050+0000"
public final static String PRE_RELEASE_GRADLE = "7.5-20211228231407+0000"
public final static String[] STANDARD_TEST_VERSIONS = [DEFAULT_GRADLE, PRE_RELEASE_GRADLE]
}

View File

@@ -44,7 +44,7 @@ class FabricAPITest extends Specification implements GradleProjectTestTrait {
setup:
def gradle = gradleProject(
repo: "https://github.com/FabricMC/fabric.git",
commit: "ce6198f63bbe0e17ba631420e9186fb72cc8b2af",
commit: "71b634e5b7845296b11be3fa6545f4fbfacc017f",
version: version,
patch: "fabric_api"
)

View File

@@ -22,34 +22,30 @@
* SOFTWARE.
*/
package net.fabricmc.loom.test.unit
package net.fabricmc.loom.test.integration
import net.fabricmc.loom.build.nesting.MergedNestedJarProvider
import net.fabricmc.loom.build.nesting.NestedJarProvider
import org.gradle.api.Project
import net.fabricmc.loom.test.util.GradleProjectTestTrait
import net.fabricmc.loom.util.ZipUtils
import spock.lang.Specification
import spock.lang.Unroll
class MergedNestedJarProviderTest extends Specification {
def "empty test"() {
given:
def nestedJarProvider = new MergedNestedJarProvider(new TestNestedJarProvider())
when:
nestedJarProvider.prepare(null)
then:
nestedJarProvider.provide() != null
}
import static net.fabricmc.loom.test.LoomTestConstants.STANDARD_TEST_VERSIONS
import static org.gradle.testkit.runner.TaskOutcome.SUCCESS
private class TestNestedJarProvider implements NestedJarProvider {
private Collection<File> files = null
class InterfaceInjectionTest extends Specification implements GradleProjectTestTrait {
@Unroll
def "interface injection (gradle #version)"() {
setup:
def gradle = gradleProject(project: "interfaceInjection", version: version)
ZipUtils.pack(new File(gradle.projectDir, "dummyDependency").toPath(), new File(gradle.projectDir, "dummy.jar").toPath())
@Override
Collection<File> provide() {
return files
}
when:
def result = gradle.run(task: "build")
@Override
void prepare(Project project) {
files = []
}
}
then:
result.task(":build").outcome == SUCCESS
where:
version << STANDARD_TEST_VERSIONS
}
}

View File

@@ -67,6 +67,9 @@ class MixinApAutoRefmapTest extends Specification implements GradleProjectTestTr
def j5 = JsonParser.parseReader(new InputStreamReader(jar.getInputStream(jar.getEntry("irrelevant.mixins.json"))))
!j5.asJsonObject.has("refmap")
def j6 = JsonParser.parseReader(new InputStreamReader(jar.getInputStream(jar.getEntry("subfolder/subfolder.mixins.json"))))
j6.asJsonObject.getAsJsonPrimitive("refmap").getAsString() == "refmap0001.json"
where:
version << STANDARD_TEST_VERSIONS
}

View File

@@ -47,7 +47,7 @@ class MixinApSimpleTest extends Specification implements GradleProjectTestTrait
result.task(":build").outcome == SUCCESS
// verify the ref-map name is correctly generated
def main = new JarFile(gradle.getOutputFile("fabric-example-mod-1.0.0-dev.jar").absoluteFile)
def main = new JarFile(new File(gradle.projectDir, "build/devlibs/fabric-example-mod-1.0.0-dev.jar").absoluteFile)
main.getEntry("main-refmap0000.json") != null
def mixin = new JarFile(gradle.getOutputFile("fabric-example-mod-1.0.0-mixin.jar").absoluteFile)
mixin.getEntry("default-refmap0000.json") != null

View File

@@ -45,9 +45,6 @@ class MultiProjectTest extends Specification implements GradleProjectTestTrait {
result.task(":core:build").outcome == SUCCESS
result.task(":example:build").outcome == SUCCESS
result.task(":remapAllJars").outcome == SUCCESS
result.task(":remapAllSources").outcome == SUCCESS
gradle.hasOutputZipEntry("multiproject-1.0.0.jar", "META-INF/jars/example-1.0.0.jar")
gradle.hasOutputZipEntry("multiproject-1.0.0.jar", "META-INF/jars/core-1.0.0.jar")
gradle.hasOutputZipEntry("multiproject-1.0.0.jar", "META-INF/jars/fabric-api-base-0.2.1+9354966b7d.jar")

View File

@@ -0,0 +1,86 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2021 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.test.integration
import net.fabricmc.loom.test.util.GradleProjectTestTrait
import spock.lang.Specification
import spock.lang.Unroll
import spock.util.environment.RestoreSystemProperties
import static net.fabricmc.loom.test.LoomTestConstants.STANDARD_TEST_VERSIONS
import static org.gradle.testkit.runner.TaskOutcome.SUCCESS
import static org.gradle.testkit.runner.TaskOutcome.UP_TO_DATE
class NativesTest extends Specification implements GradleProjectTestTrait {
@Unroll
def "Default natives for 1.18 (gradle #version)"() {
setup:
def gradle = gradleProject(project: "minimalBase", version: version)
gradle.buildGradle << '''
dependencies {
minecraft "com.mojang:minecraft:1.18"
mappings loom.officialMojangMappings()
}
'''
when:
// Run the task twice to ensure its up to date
def result = gradle.run(task: "extractNatives")
def result2 = gradle.run(task: "extractNatives")
then:
result.task(":extractNatives").outcome == SUCCESS
result2.task(":extractNatives").outcome == UP_TO_DATE
where:
version << STANDARD_TEST_VERSIONS
}
@RestoreSystemProperties
@Unroll
def "Overridden natives for 1.18 (gradle #version)"() {
setup:
System.setProperty('loom.test.lwjgloverride', "true")
def gradle = gradleProject(project: "minimalBase", version: version)
gradle.buildGradle << '''
dependencies {
minecraft "com.mojang:minecraft:1.18"
mappings loom.officialMojangMappings()
}
'''
when:
// Run the task twice to ensure its up to date
def result = gradle.run(task: "extractNatives")
then:
result.task(":extractNatives").outcome == SUCCESS
where:
version << STANDARD_TEST_VERSIONS
}
}

View File

@@ -0,0 +1,57 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2021 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.test.integration
import net.fabricmc.loom.test.util.GradleProjectTestTrait
import spock.lang.Specification
import spock.lang.Unroll
import static net.fabricmc.loom.test.LoomTestConstants.STANDARD_TEST_VERSIONS
import static org.gradle.testkit.runner.TaskOutcome.SUCCESS
// Basic test to just ensure it builds.
class SignatureFixesTest extends Specification implements GradleProjectTestTrait {
@Unroll
def "build (gradle #version)"() {
setup:
def gradle = gradleProject(project: "minimalBase", version: version)
gradle.buildGradle << '''
dependencies {
minecraft "com.mojang:minecraft:1.18-pre1"
mappings "net.fabricmc:yarn:1.18-pre1+build.14:v2"
}
'''
when:
def result = gradle.run(task: "build")
then:
result.task(":build").outcome == SUCCESS
where:
version << STANDARD_TEST_VERSIONS
}
}

View File

@@ -51,6 +51,7 @@ class SimpleProjectTest extends Specification implements GradleProjectTestTrait
then:
result.task(":build").outcome == SUCCESS
gradle.getOutputZipEntry("fabric-example-mod-1.0.0.jar", "META-INF/MANIFEST.MF").contains("Fabric-Loom-Version: 0.0.0+unknown")
gradle.getOutputZipEntry("fabric-example-mod-1.0.0-sources.jar", "net/fabricmc/example/mixin/ExampleMixin.java").contains("class_442") // Very basic test to ensure sources got remapped
serverResult.successful()
serverResult.output.contains("Hello simple Fabric mod") // A check to ensure our mod init was actually called

View File

@@ -0,0 +1,104 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2021 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.test.unit
import net.fabricmc.loom.configuration.ide.RunConfig
import net.fabricmc.loom.configuration.ide.idea.IdeaSyncTask
import org.intellij.lang.annotations.Language
import spock.lang.Specification
import java.nio.charset.StandardCharsets
class IdeaClasspathModificationsTest extends Specification {
def "configure exclusions"() {
when:
def input = fromDummy()
def output = IdeaSyncTask.setClasspathModificationsInXml(input, ["/path/to/file.jar"])
then:
output == EXPECTED
}
def "re-configure exclusions"() {
when:
def input = fromDummy()
def output = IdeaSyncTask.setClasspathModificationsInXml(input, ["/path/to/file.jar"])
output = IdeaSyncTask.setClasspathModificationsInXml(output, ["/path/to/file.jar", "/path/to/another.jar"])
then:
output == EXPECTED2
}
private String fromDummy() {
String dummyConfig
IdeaSyncTask.class.getClassLoader().getResourceAsStream("idea_run_config_template.xml").withCloseable {
dummyConfig = new String(it.readAllBytes(), StandardCharsets.UTF_8)
}
dummyConfig = dummyConfig.replace("%NAME%", "Minecraft Client")
dummyConfig = dummyConfig.replace("%MAIN_CLASS%", "net.minecraft.client.Main")
dummyConfig = dummyConfig.replace("%IDEA_MODULE%", "main.test")
dummyConfig = dummyConfig.replace("%RUN_DIRECTORY%", ".run")
dummyConfig = dummyConfig.replace("%PROGRAM_ARGS%", RunConfig.joinArguments([]).replaceAll("\"", "&quot;"))
dummyConfig = dummyConfig.replace("%VM_ARGS%", RunConfig.joinArguments([]).replaceAll("\"", "&quot;"))
return dummyConfig
}
@Language("XML")
private static final String EXPECTED = '''
<component name="ProjectRunConfigurationManager">
<configuration default="false" factoryName="Application" name="Minecraft Client" type="Application">
<option name="MAIN_CLASS_NAME" value="net.minecraft.client.Main"/>
<module name="main.test"/>
<option name="PROGRAM_PARAMETERS" value=""/>
<option name="VM_PARAMETERS" value=""/>
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/.run/"/>
<method v="2">
<option enabled="true" name="Make"/>
</method>
<classpathModifications><entry exclude="true" path="/path/to/file.jar"/></classpathModifications></configuration>
</component>
'''.trim()
@Language("XML")
private static final String EXPECTED2 = '''
<component name="ProjectRunConfigurationManager">
<configuration default="false" factoryName="Application" name="Minecraft Client" type="Application">
<option name="MAIN_CLASS_NAME" value="net.minecraft.client.Main"/>
<module name="main.test"/>
<option name="PROGRAM_PARAMETERS" value=""/>
<option name="VM_PARAMETERS" value=""/>
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/.run/"/>
<method v="2">
<option enabled="true" name="Make"/>
</method>
<classpathModifications><entry exclude="true" path="/path/to/file.jar"/><entry exclude="true" path="/path/to/another.jar"/></classpathModifications></configuration>
</component>
'''.trim()
}

View File

@@ -33,6 +33,6 @@ class RunConfigUnitTest extends Specification {
def args = RunConfig.joinArguments(["-Dfabric.test=123", "-Dfabric.test=abc 123"])
then:
args == '"-Dfabric.test=123" "-Dfabric.test=abc 123"'
args == '-Dfabric.test=123 "-Dfabric.test=abc 123"'
}
}

View File

@@ -30,28 +30,29 @@ import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingSpecBuil
import net.fabricmc.loom.configuration.providers.mappings.intermediary.IntermediaryMappingsSpec
import net.fabricmc.loom.configuration.providers.mappings.mojmap.MojangMappingsSpec
import net.fabricmc.loom.configuration.providers.mappings.parchment.ParchmentMappingsSpec
import org.gradle.api.Action
import org.gradle.util.ConfigureUtil
import net.fabricmc.loom.util.ClosureAction
import spock.lang.Specification
class LayeredMappingSpecBuilderTest extends LayeredMappingsSpecification {
def "simple mojmap" () {
when:
def spec = layered() {
def spec = layered {
officialMojangMappings()
}
def layers = spec.layers()
then:
spec.version == "layered+hash.2198"
layers.size() == 2
spec.version == "layered+hash.2198"
layers[0].class == IntermediaryMappingsSpec
layers[1].class == MojangMappingsSpec
}
def "simple mojmap with parchment" () {
when:
def dep = "I like cake"
def spec = layered() {
officialMojangMappings()
parchment("I like cake")
parchment(dep)
}
def layers = spec.layers()
def parchment = layers[2] as ParchmentMappingsSpec
@@ -87,7 +88,7 @@ class LayeredMappingSpecBuilderTest extends LayeredMappingsSpecification {
def "simple mojmap with parchment keep prefix alternate hash" () {
when:
def spec = layered() {
def spec = layered {
officialMojangMappings()
parchment("I really like cake") {
it.removePrefix = false
@@ -105,14 +106,9 @@ class LayeredMappingSpecBuilderTest extends LayeredMappingsSpecification {
parchment.removePrefix() == false
}
// Gradle does this big of magic behind the scenes
LayeredMappingSpec layered(@DelegatesTo(LayeredMappingSpecBuilderImpl) Closure cl) {
return layeredAction(ConfigureUtil.configureUsing(cl))
}
LayeredMappingSpec layeredAction(Action<LayeredMappingSpecBuilderImpl> action) {
LayeredMappingSpecBuilderImpl builder = new LayeredMappingSpecBuilderImpl(null)
action.execute(builder)
new ClosureAction(cl).execute(builder)
return builder.build()
}
}

View File

@@ -1,6 +1,6 @@
diff --git a/build.gradle b/build.gradle
--- a/build.gradle (revision ce6198f63bbe0e17ba631420e9186fb72cc8b2af)
+++ b/build.gradle (date 1637848132986)
--- a/build.gradle (revision 71b634e5b7845296b11be3fa6545f4fbfacc017f)
+++ b/build.gradle (date 1638654919842)
@@ -31,17 +31,7 @@
throw new NullPointerException("Could not find version for " + project.name)
}
@@ -20,43 +20,3 @@ diff --git a/build.gradle b/build.gradle
}
def getBranch() {
@@ -132,9 +122,8 @@
include "**/*.java"
}
- task sourcesJar(type: Jar, dependsOn: classes) {
- archiveClassifier = "sources"
- from sourceSets.main.allSource
+ java {
+ withSourcesJar()
}
checkstyle {
@@ -229,12 +218,16 @@
publications {
mavenJava(MavenPublication) {
from components.java
+
+ artifact javadocJar
}
}
setupRepositories(repositories)
}
+ loom.disableDeprecatedPomGeneration(publishing.publications.mavenJava)
+
javadoc.enabled = false
afterEvaluate {
@@ -242,10 +235,6 @@
genSourcesWithFernFlower.enabled = false
genSourcesWithCfr.enabled = false
unpickJar.enabled = false
-
- // Work around a loom bug causing empty jars to be pushed to maven local.
- publishMavenJavaPublicationToMavenLocal.dependsOn rootProject.tasks.getByName("remapAllJars")
- publishMavenJavaPublicationToMavenLocal.dependsOn rootProject.tasks.getByName("remapAllSources")
}
}

View File

@@ -7,3 +7,7 @@ dependencies {
mappings "net.fabricmc:yarn:21w38a+build.11:v2"
modImplementation "net.fabricmc:fabric-loader:0.11.7"
}
tasks.named("genSourcesWithCfr") {
options.put("test", "value")
}

View File

@@ -0,0 +1,18 @@
// This is used by a range of tests that append to this file before running the gradle tasks.
// Can be used for tests that require minimal custom setup
plugins {
id 'fabric-loom'
id 'maven-publish'
}
archivesBaseName = "fabric-example-mod"
version = "1.0.0"
group = "com.example"
dependencies {
minecraft "com.mojang:minecraft:1.17.1"
mappings "net.fabricmc:yarn:1.17.1+build.59:v2"
modImplementation "net.fabricmc:fabric-loader:0.11.6"
modImplementation files("dummy.jar")
}

View File

@@ -0,0 +1,12 @@
{
"schemaVersion": 1,
"id": "dummy",
"version": "1",
"name": "Dummy Mod",
"custom": {
"fabric-api:module-lifecycle": "stable",
"loom:injected_interfaces": {
"net/minecraft/class_2248": ["InjectedInterface"]
}
}
}

View File

@@ -0,0 +1,10 @@
import net.minecraft.block.Blocks;
import net.fabricmc.api.ModInitializer;
public class ExampleMod implements ModInitializer {
@Override
public void onInitialize() {
Blocks.AIR.newMethodThatDidNotExist();
}
}

View File

@@ -0,0 +1,5 @@
public interface InjectedInterface {
default void newMethodThatDidNotExist() {
}
}

View File

@@ -0,0 +1,15 @@
package net.fabricmc.example.mixin;
import net.minecraft.client.gui.screen.ChatScreen;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ChatScreen.class)
public class ExampleMixinSubfolder {
@Inject(at = @At("HEAD"), method = "init()V")
private void init(CallbackInfo info) {
System.out.println("This line is printed by an example mod mixin!");
}
}

View File

@@ -24,6 +24,7 @@
},
"mixins": [
"main.mixins.json",
"subfolder/subfolder.mixins.json",
{
"config": "blabla.json",
"environment": "client"

View File

@@ -0,0 +1,14 @@
{
"required": true,
"minVersion": "0.8",
"package": "net.fabricmc.example.mixin",
"compatibilityLevel": "JAVA_16",
"mixins": [
],
"client": [
"ExampleMixinSubfolder"
],
"injectors": {
"defaultRequire": 1
}
}

View File

@@ -31,7 +31,7 @@ archivesBaseName = "fabric-example-mod"
version = "1.0.0"
dependencies {
minecraft "com.mojang:minecraft:1.16.5"
mappings "net.fabricmc:yarn:1.16.5+build.5:v2"
modImplementation "net.fabricmc:fabric-loader:0.11.2"
minecraft "com.mojang:minecraft:1.18.1"
mappings "net.fabricmc:yarn:1.18.1+build.12:v2"
modImplementation "net.fabricmc:fabric-loader:0.12.12"
}

View File

@@ -29,6 +29,9 @@ dependencies {
// PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs.
// You may need to force-disable transitiveness on them.
// Example include
include "org.xerial:sqlite-jdbc:3.36.0.3"
}
tasks.withType(JavaCompile).configureEach {