Merge remote-tracking branch 'FabricMC/exp/1.6' into exp/1.6

# Conflicts:
#	.github/workflows/publish.yml
#	build.gradle
#	gradle/libs.versions.toml
#	src/main/java/net/fabricmc/loom/LoomGradleExtension.java
#	src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java
#	src/main/java/net/fabricmc/loom/configuration/ifaceinject/InterfaceInjectionProcessor.java
#	src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java
#	src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java
#	src/main/java/net/fabricmc/loom/task/GenVsCodeProjectTask.java
#	src/test/resources/projects/kotlin/build.gradle.kts
This commit is contained in:
shedaniel
2024-02-29 18:46:30 +09:00
53 changed files with 1592 additions and 221 deletions

View File

@@ -15,6 +15,8 @@ import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.signature.SignatureReader;
import org.objectweb.asm.util.CheckSignatureAdapter;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.configuration.ifaceinject.InterfaceInjectionProcessor;
@@ -92,10 +94,25 @@ public final class ArchitecturyCommonJson implements JsonBackedModMetadataFile,
final List<InterfaceInjectionProcessor.InjectedInterface> result = new ArrayList<>();
for (String className : addedIfaces.keySet()) {
final JsonArray ifaceNames = addedIfaces.getAsJsonArray(className);
final JsonArray ifacesInfo = addedIfaces.getAsJsonArray(className);
for (JsonElement ifaceName : ifaceNames) {
result.add(new InterfaceInjectionProcessor.InjectedInterface(modId, className, ifaceName.getAsString()));
for (JsonElement ifaceElement : ifacesInfo) {
String ifaceInfo = ifaceElement.getAsString();
String name = ifaceInfo;
String generics = null;
if (ifaceInfo.contains("<") && ifaceInfo.contains(">")) {
name = ifaceInfo.substring(0, ifaceInfo.indexOf("<"));
generics = ifaceInfo.substring(ifaceInfo.indexOf("<"));
// First Generics Check, if there are generics, are them correctly written?
SignatureReader reader = new SignatureReader("Ljava/lang/Object" + generics + ";");
CheckSignatureAdapter checker = new CheckSignatureAdapter(CheckSignatureAdapter.CLASS_SIGNATURE, null);
reader.accept(checker);
}
result.add(new InterfaceInjectionProcessor.InjectedInterface(modId, className, name, generics));
}
}

View File

@@ -25,6 +25,7 @@
package net.fabricmc.loom;
import java.nio.file.Path;
import java.util.Collection;
import java.util.List;
import org.gradle.api.Project;
@@ -46,6 +47,7 @@ import net.fabricmc.loom.configuration.providers.forge.ForgeUserdevProvider;
import net.fabricmc.loom.configuration.providers.forge.PatchProvider;
import net.fabricmc.loom.configuration.providers.forge.SrgProvider;
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.McpConfigProvider;
import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingsFactory;
import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.library.LibraryProcessorManager;
@@ -54,6 +56,7 @@ import net.fabricmc.loom.configuration.providers.minecraft.mapped.MojangMappedMi
import net.fabricmc.loom.configuration.providers.minecraft.mapped.NamedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.SrgMinecraftProvider;
import net.fabricmc.loom.extension.LoomFiles;
import net.fabricmc.loom.extension.LoomProblemReporter;
import net.fabricmc.loom.extension.MixinExtension;
import net.fabricmc.loom.extension.RemapperExtensionHolder;
import net.fabricmc.loom.util.ModPlatform;
@@ -146,6 +149,10 @@ public interface LoomGradleExtension extends LoomGradleExtensionAPI {
ListProperty<RemapperExtensionHolder> getRemapperExtensions();
Collection<LayeredMappingsFactory> getLayeredMappingFactories();
LoomProblemReporter getProblemReporter();
// ===================
// Architectury Loom
// ===================

View File

@@ -143,6 +143,10 @@ public class LoomRepositoryPlugin implements Plugin<PluginAware> {
sources.artifact();
sources.ignoreGradleMetadataRedirection();
});
// Fallback to maven central for artifacts such as sources or javadocs that are not mirrored on Mojang's repo.
// See: https://github.com/FabricMC/fabric-loom/issues/1032
repo.artifactUrls(ArtifactRepositoryContainer.MAVEN_CENTRAL_URL);
});
}

View File

@@ -31,12 +31,20 @@ import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import javax.inject.Inject;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
import org.gradle.api.file.FileCollection;
import org.gradle.api.logging.LogLevel;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.tasks.AbstractCopyTask;
@@ -67,6 +75,7 @@ import net.fabricmc.loom.configuration.providers.forge.PatchProvider;
import net.fabricmc.loom.configuration.providers.forge.SrgProvider;
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.McpConfigProvider;
import net.fabricmc.loom.configuration.providers.forge.minecraft.ForgeMinecraftProvider;
import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingsFactory;
import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJarConfiguration;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
@@ -108,8 +117,10 @@ public abstract class CompileConfiguration implements Runnable {
final boolean previousRefreshDeps = extension.refreshDeps();
if (getAndLock()) {
getProject().getLogger().lifecycle("Found existing cache lock file, rebuilding loom cache. This may have been caused by a failed or canceled build.");
final LockResult lockResult = acquireProcessLockWaiting(getLockFile());
if (lockResult != LockResult.ACQUIRED_CLEAN) {
getProject().getLogger().lifecycle("Found existing cache lock file ({}), rebuilding loom cache. This may have been caused by a failed or canceled build.", lockResult);
extension.setRefreshDeps(true);
}
@@ -197,6 +208,9 @@ public abstract class CompileConfiguration implements Runnable {
extension.setMinecraftProvider(minecraftProvider);
minecraftProvider.provide();
// Created any layered mapping files.
LayeredMappingsFactory.afterEvaluate(configContext);
// This needs to run after MinecraftProvider.initFiles and MinecraftLibraryProvider.provide
// but before MinecraftPatchedProvider.provide.
setupDependencyProviders(project, extension);
@@ -318,31 +332,180 @@ public abstract class CompileConfiguration implements Runnable {
.afterEvaluation();
}
private Path getLockFile() {
private LockFile getLockFile() {
final LoomGradleExtension extension = LoomGradleExtension.get(getProject());
final Path cacheDirectory = extension.getFiles().getUserCache().toPath();
final String pathHash = Checksum.projectHash(getProject());
return cacheDirectory.resolve("." + pathHash + ".lock");
return new LockFile(
cacheDirectory.resolve("." + pathHash + ".lock"),
"Lock for cache='%s', project='%s'".formatted(
cacheDirectory, getProject().absoluteProjectPath(getProject().getPath())
)
);
}
private boolean getAndLock() {
final Path lock = getLockFile();
if (Files.exists(lock)) {
return true;
record LockFile(Path file, String description) {
@Override
public String toString() {
return this.description;
}
}
enum LockResult {
// acquired immediately or after waiting for another process to release
ACQUIRED_CLEAN,
// already owned by current pid
ACQUIRED_ALREADY_OWNED,
// acquired due to current owner not existing
ACQUIRED_PREVIOUS_OWNER_MISSING
}
private LockResult acquireProcessLockWaiting(LockFile lockFile) {
// one hour
return this.acquireProcessLockWaiting(lockFile, Duration.ofHours(1));
}
private LockResult acquireProcessLockWaiting(LockFile lockFile, Duration timeout) {
try {
Files.createFile(lock);
} catch (IOException e) {
throw new UncheckedIOException("Failed to acquire getProject() configuration lock", e);
return this.acquireProcessLockWaiting_(lockFile, timeout);
} catch (final IOException e) {
throw new RuntimeException("Exception acquiring lock " + lockFile, e);
}
}
// Returns true if our process already owns the lock
@SuppressWarnings("BusyWait")
private LockResult acquireProcessLockWaiting_(LockFile lockFile, Duration timeout) throws IOException {
final long timeoutMs = timeout.toMillis();
final Logger logger = Logging.getLogger("loom_acquireProcessLockWaiting");
final long currentPid = ProcessHandle.current().pid();
boolean abrupt = false;
if (Files.exists(lockFile.file)) {
long lockingProcessId;
try {
lockingProcessId = Long.parseLong(Files.readString(lockFile.file));
} catch (final Exception e) {
lockingProcessId = -1;
}
if (lockingProcessId == currentPid) {
return LockResult.ACQUIRED_ALREADY_OWNED;
}
logger.lifecycle("\"{}\" is currently held by pid '{}'.", lockFile, lockingProcessId);
Optional<ProcessHandle> handle = ProcessHandle.of(lockingProcessId);
if (handle.isEmpty()) {
logger.lifecycle("Locking process does not exist, assuming abrupt termination and deleting lock file.");
Files.deleteIfExists(lockFile.file);
abrupt = true;
} else {
logger.lifecycle(printWithParents(handle.get()));
logger.lifecycle("Waiting for lock to be released...");
long sleptMs = 0;
while (Files.exists(lockFile.file)) {
try {
Thread.sleep(100);
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
}
sleptMs += 100;
if (sleptMs >= 1000 * 60 && sleptMs % (1000 * 60) == 0L) {
logger.lifecycle(
"""
Have been waiting on "{}" held by pid '{}' for {} minute(s).
If this persists for an unreasonable length of time, kill this process, run './gradlew --stop' and then try again.""",
lockFile, lockingProcessId, sleptMs / 1000 / 60
);
}
if (sleptMs >= timeoutMs) {
throw new GradleException("Have been waiting on lock file '%s' for %s ms. Giving up as timeout is %s ms."
.formatted(lockFile, sleptMs, timeoutMs));
}
}
}
}
return false;
if (!Files.exists(lockFile.file.getParent())) {
Files.createDirectories(lockFile.file.getParent());
}
Files.writeString(lockFile.file, String.valueOf(currentPid));
return abrupt ? LockResult.ACQUIRED_PREVIOUS_OWNER_MISSING : LockResult.ACQUIRED_CLEAN;
}
private String printWithParents(ProcessHandle processHandle) {
var output = new StringBuilder();
List<ProcessHandle> chain = getParentChain(null, processHandle);
for (int i = 0; i < chain.size(); i++) {
ProcessHandle handle = chain.get(i);
output.append("\t".repeat(i));
if (i != 0) {
output.append("└─ ");
}
output.append(getInfoString(handle));
if (i < chain.size() - 1) {
output.append('\n');
}
}
return output.toString();
}
private String getInfoString(ProcessHandle handle) {
return "(%s) pid %s '%s%s'%s".formatted(
handle.info().user().orElse("unknown user"),
handle.pid(),
handle.info().command().orElse("unknown command"),
handle.info().arguments().map(arr -> {
if (getProject().getGradle().getStartParameter().getLogLevel() != LogLevel.INFO
&& getProject().getGradle().getStartParameter().getLogLevel() != LogLevel.DEBUG) {
return " (run with --info or --debug to show arguments, may reveal sensitive info)";
}
String join = String.join(" ", arr);
if (join.isBlank()) {
return "";
}
return " " + join;
}).orElse(" (unknown arguments)"),
handle.info().startInstant().map(instant -> " started at " + instant).orElse("")
);
}
private List<ProcessHandle> getParentChain(List<ProcessHandle> collectTo, ProcessHandle processHandle) {
if (collectTo == null) {
collectTo = new ArrayList<>();
}
Optional<ProcessHandle> parent = processHandle.parent();
if (parent.isPresent()) {
getParentChain(collectTo, parent.get());
}
collectTo.add(processHandle);
return collectTo;
}
private void releaseLock() {
final Path lock = getLockFile();
final Path lock = getLockFile().file;
if (!Files.exists(lock)) {
return;

View File

@@ -32,8 +32,11 @@ import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.DependencySet;
import org.gradle.api.artifacts.FileCollectionDependency;
import org.gradle.api.artifacts.ResolvedDependency;
import org.gradle.api.artifacts.SelfResolvingDependency;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.util.gradle.SelfResolvingDependencyUtils;
public class DependencyInfo {
final Project project;
@@ -61,8 +64,11 @@ public class DependencyInfo {
}
public static DependencyInfo create(Project project, Dependency dependency, Configuration sourceConfiguration) {
if (dependency instanceof SelfResolvingDependency selfResolvingDependency) {
return new FileDependencyInfo(project, selfResolvingDependency, sourceConfiguration);
if (SelfResolvingDependencyUtils.isExplicitSRD(dependency)) {
LoomGradleExtension.get(project).getProblemReporter().reportSelfResolvingDependencyUsage();
return FileDependencyInfo.createForDeprecatedSRD(project, dependency, sourceConfiguration);
} else if (dependency instanceof FileCollectionDependency fileCollectionDependency) {
return new FileDependencyInfo(project, fileCollectionDependency, sourceConfiguration);
} else {
return new DependencyInfo(project, dependency, sourceConfiguration);
}
@@ -99,10 +105,6 @@ public class DependencyInfo {
}
public Set<File> resolve() {
if (dependency instanceof SelfResolvingDependency selfResolvingDependency) {
return selfResolvingDependency.resolve();
}
return sourceConfiguration.files(dependency);
}

View File

@@ -42,19 +42,24 @@ import org.apache.commons.io.FilenameUtils;
import org.gradle.api.InvalidUserDataException;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.SelfResolvingDependency;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.FileCollectionDependency;
import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.loom.util.gradle.SelfResolvingDependencyUtils;
public class FileDependencyInfo extends DependencyInfo {
protected final Map<String, File> classifierToFile = new HashMap<>();
protected final Set<File> resolvedFiles;
protected final String group, name, version;
FileDependencyInfo(Project project, SelfResolvingDependency dependency, Configuration configuration) {
FileDependencyInfo(Project project, FileCollectionDependency dependency, Configuration configuration) {
this(project, dependency, configuration, dependency.getFiles().getFiles());
}
private FileDependencyInfo(Project project, Dependency dependency, Configuration configuration, Set<File> files) {
super(project, dependency, configuration);
Set<File> files = dependency.resolve();
this.resolvedFiles = files;
switch (files.size()) {
case 0 -> //Don't think Gradle would ever let you do this
@@ -147,6 +152,15 @@ public class FileDependencyInfo extends DependencyInfo {
}
}
@Deprecated // Remove in Gradle 9
public static FileDependencyInfo createForDeprecatedSRD(Project project, Dependency dependency, Configuration configuration) {
if (!SelfResolvingDependencyUtils.isExplicitSRD(dependency)) {
throw new IllegalArgumentException("Dependency is a FileCollectionDependency");
}
return new FileDependencyInfo(project, dependency, configuration, SelfResolvingDependencyUtils.resolve(dependency));
}
@Override
public String getResolvedVersion() {
return version;

View File

@@ -46,6 +46,9 @@ import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.signature.SignatureReader;
import org.objectweb.asm.signature.SignatureVisitor;
import org.objectweb.asm.util.CheckSignatureAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -54,12 +57,15 @@ import net.fabricmc.loom.api.processor.MinecraftJarProcessor;
import net.fabricmc.loom.api.processor.ProcessorContext;
import net.fabricmc.loom.api.processor.SpecContext;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.LazyCloseable;
import net.fabricmc.loom.util.Pair;
import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.loom.util.fmj.FabricModJson;
import net.fabricmc.loom.util.fmj.ModMetadataFabricModJson;
import net.fabricmc.mappingio.tree.MappingTree;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
import net.fabricmc.tinyremapper.TinyRemapper;
import net.fabricmc.tinyremapper.api.TrRemapper;
public abstract class InterfaceInjectionProcessor implements MinecraftJarProcessor<InterfaceInjectionProcessor.Spec> {
private static final Logger LOGGER = LoggerFactory.getLogger(InterfaceInjectionProcessor.class);
@@ -106,22 +112,36 @@ public abstract class InterfaceInjectionProcessor implements MinecraftJarProcess
final MemoryMappingTree mappings = context.getMappings();
final int intermediaryIndex = mappings.getNamespaceId(MappingsNamespace.INTERMEDIARY.toString());
final int namedIndex = mappings.getNamespaceId(MappingsNamespace.NAMED.toString());
final List<InjectedInterface> remappedInjectedInterfaces = spec.injectedInterfaces().stream()
.map(injectedInterface -> remap(injectedInterface, s -> mappings.mapClassName(s, intermediaryIndex, namedIndex)))
.toList();
try {
ZipUtils.transform(jar, getTransformers(remappedInjectedInterfaces));
} catch (IOException e) {
throw new RuntimeException("Failed to apply interface injections to " + jar, e);
try (LazyCloseable<TinyRemapper> tinyRemapper = context.createRemapper(MappingsNamespace.INTERMEDIARY, MappingsNamespace.NAMED)) {
final List<InjectedInterface> remappedInjectedInterfaces = spec.injectedInterfaces().stream()
.map(injectedInterface -> remap(
injectedInterface,
s -> mappings.mapClassName(s, intermediaryIndex, namedIndex),
tinyRemapper.get().getEnvironment().getRemapper()
))
.toList();
try {
ZipUtils.transform(jar, getTransformers(remappedInjectedInterfaces));
} catch (IOException e) {
throw new RuntimeException("Failed to apply interface injections to " + jar, e);
}
}
}
private InjectedInterface remap(InjectedInterface in, Function<String, String> remapper) {
private InjectedInterface remap(InjectedInterface in, Function<String, String> remapper, TrRemapper signatureRemapper) {
String generics = null;
if (in.generics() != null) {
String fakeSignature = signatureRemapper.mapSignature("Ljava/lang/Object" + in.generics() + ";", false); // Turning the raw generics string into a fake signature
generics = fakeSignature.substring("Ljava/lang/Object".length(), fakeSignature.length() - 1); // Retrieving the remapped raw generics string from the remapped fake signature
}
return new InjectedInterface(
in.modId(),
remapper.apply(in.className()),
remapper.apply(in.ifaceName())
remapper.apply(in.ifaceName()),
generics
);
}
@@ -197,7 +217,7 @@ public abstract class InterfaceInjectionProcessor implements MinecraftJarProcess
return comment;
}
public record InjectedInterface(String modId, String className, String ifaceName) {
public record InjectedInterface(String modId, String className, String ifaceName, @Nullable String generics) {
public static List<InjectedInterface> fromMod(FabricModJson fabricModJson) {
if (fabricModJson instanceof ModMetadataFabricModJson modMetadataFmj) {
return modMetadataFmj.getModMetadata().getInjectedInterfaces(modMetadataFmj.getId());
@@ -215,10 +235,25 @@ public abstract class InterfaceInjectionProcessor implements MinecraftJarProcess
final List<InjectedInterface> result = new ArrayList<>();
for (String className : addedIfaces.keySet()) {
final JsonArray ifaceNames = addedIfaces.getAsJsonArray(className);
final JsonArray ifacesInfo = addedIfaces.getAsJsonArray(className);
for (JsonElement ifaceName : ifaceNames) {
result.add(new InjectedInterface(modId, className, ifaceName.getAsString()));
for (JsonElement ifaceElement : ifacesInfo) {
String ifaceInfo = ifaceElement.getAsString();
String name = ifaceInfo;
String generics = null;
if (ifaceInfo.contains("<") && ifaceInfo.contains(">")) {
name = ifaceInfo.substring(0, ifaceInfo.indexOf("<"));
generics = ifaceInfo.substring(ifaceInfo.indexOf("<"));
// First Generics Check, if there are generics, are them correctly written?
SignatureReader reader = new SignatureReader("Ljava/lang/Object" + generics + ";");
CheckSignatureAdapter checker = new CheckSignatureAdapter(CheckSignatureAdapter.CLASS_SIGNATURE, null);
reader.accept(checker);
}
result.add(new InjectedInterface(modId, className, name, generics));
}
}
@@ -231,6 +266,16 @@ public abstract class InterfaceInjectionProcessor implements MinecraftJarProcess
.flatMap(List::stream)
.toList();
}
public static boolean containsGenerics(List<InjectedInterface> injectedInterfaces) {
for (InjectedInterface injectedInterface : injectedInterfaces) {
if (injectedInterface.generics() != null) {
return true;
}
}
return false;
}
}
private static class InjectingClassVisitor extends ClassVisitor {
@@ -246,6 +291,7 @@ public abstract class InterfaceInjectionProcessor implements MinecraftJarProcess
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
String[] baseInterfaces = interfaces.clone();
Set<String> modifiedInterfaces = new LinkedHashSet<>(interfaces.length + injectedInterfaces.size());
Collections.addAll(modifiedInterfaces, interfaces);
@@ -254,11 +300,35 @@ public abstract class InterfaceInjectionProcessor implements MinecraftJarProcess
}
// See JVMS: https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-4.html#jvms-ClassSignature
if (InjectedInterface.containsGenerics(injectedInterfaces) && signature == null) {
// Classes that are not using generics don't need signatures, so their signatures are null
// If the class is not using generics but that an injected interface targeting the class is using them, we are creating the class signature
StringBuilder baseSignatureBuilder = new StringBuilder("L" + superName + ";");
for (String baseInterface : baseInterfaces) {
baseSignatureBuilder.append("L").append(baseInterface).append(";");
}
signature = baseSignatureBuilder.toString();
}
if (signature != null) {
SignatureReader reader = new SignatureReader(signature);
// Second Generics Check, if there are passed generics, are all of them present in the target class?
GenericsChecker checker = new GenericsChecker(Constants.ASM_VERSION, injectedInterfaces);
reader.accept(checker);
var resultingSignature = new StringBuilder(signature);
for (InjectedInterface injectedInterface : injectedInterfaces) {
String superinterfaceSignature = "L" + injectedInterface.ifaceName() + ";";
String superinterfaceSignature;
if (injectedInterface.generics() != null) {
superinterfaceSignature = "L" + injectedInterface.ifaceName() + injectedInterface.generics() + ";";
} else {
superinterfaceSignature = "L" + injectedInterface.ifaceName() + ";";
}
if (resultingSignature.indexOf(superinterfaceSignature) == -1) {
resultingSignature.append(superinterfaceSignature);
@@ -319,4 +389,72 @@ public abstract class InterfaceInjectionProcessor implements MinecraftJarProcess
super.visitEnd();
}
}
private static class GenericsChecker extends SignatureVisitor {
private final List<String> typeParameters;
private final List<InjectedInterface> injectedInterfaces;
GenericsChecker(int asmVersion, List<InjectedInterface> injectedInterfaces) {
super(asmVersion);
this.typeParameters = new ArrayList<>();
this.injectedInterfaces = injectedInterfaces;
}
@Override
public void visitFormalTypeParameter(String name) {
this.typeParameters.add(name);
super.visitFormalTypeParameter(name);
}
@Override
public void visitEnd() {
for (InjectedInterface injectedInterface : this.injectedInterfaces) {
if (injectedInterface.generics() != null) {
SignatureReader reader = new SignatureReader("Ljava/lang/Object" + injectedInterface.generics() + ";");
GenericsConfirm confirm = new GenericsConfirm(
Constants.ASM_VERSION,
injectedInterface.className(),
injectedInterface.ifaceName(),
this.typeParameters
);
reader.accept(confirm);
}
}
super.visitEnd();
}
public static class GenericsConfirm extends SignatureVisitor {
private final String className;
private final String interfaceName;
private final List<String> acceptedTypeVariables;
GenericsConfirm(int asmVersion, String className, String interfaceName, List<String> acceptedTypeVariables) {
super(asmVersion);
this.className = className;
this.interfaceName = interfaceName;
this.acceptedTypeVariables = acceptedTypeVariables;
}
@Override
public void visitTypeVariable(String name) {
if (!this.acceptedTypeVariables.contains(name)) {
throw new IllegalStateException(
"Interface "
+ this.interfaceName
+ " attempted to use a type variable named "
+ name
+ " which is not present in the "
+ this.className
+ " class"
);
}
super.visitTypeVariable(name);
}
}
}
}

View File

@@ -27,25 +27,42 @@ package net.fabricmc.loom.configuration.providers.mappings;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import javax.inject.Inject;
import com.google.common.net.UrlEscapers;
import org.gradle.api.Action;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.DependencyArtifact;
import org.gradle.api.artifacts.ModuleDependency;
import org.gradle.api.artifacts.dsl.DependencyFactory;
import org.gradle.api.provider.Property;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.loom.api.mappings.intermediate.IntermediateMappingsProvider;
import net.fabricmc.loom.extension.LoomGradleExtensionApiImpl;
public abstract class IntermediaryMappingsProvider extends IntermediateMappingsProvider {
@ApiStatus.Internal
public abstract class IntermediaryMappingsProvider extends IntermediateMappingsProviderInternal {
public static final String NAME = "intermediary-v2";
private static final String FABRIC_INTERMEDIARY_GROUP_NAME = "net.fabricmc:intermediary";
private static final Logger LOGGER = LoggerFactory.getLogger(IntermediateMappingsProvider.class);
public abstract Property<String> getIntermediaryUrl();
public abstract Property<Boolean> getRefreshDeps();
@Inject
public abstract DependencyFactory getDependencyFactory();
@Override
public void provide(Path tinyMappings) throws IOException {
public void provide(Path tinyMappings, @Nullable Project project) throws IOException {
if (Files.exists(tinyMappings) && !getRefreshDeps().get()) {
return;
}
@@ -53,16 +70,37 @@ public abstract class IntermediaryMappingsProvider extends IntermediateMappingsP
// Download and extract intermediary
final Path intermediaryJarPath = Files.createTempFile(getName(), ".jar");
final String encodedMcVersion = UrlEscapers.urlFragmentEscaper().escape(getMinecraftVersion().get());
final String url = getIntermediaryUrl().get().formatted(encodedMcVersion);
final String urlRaw = getIntermediaryUrl().get();
LOGGER.info("Downloading intermediary from {}", url);
if (project != null && urlRaw.equals(LoomGradleExtensionApiImpl.DEFAULT_INTERMEDIARY_URL)) {
final ModuleDependency intermediaryDep = getDependencyFactory()
.create(FABRIC_INTERMEDIARY_GROUP_NAME + ':' + encodedMcVersion);
intermediaryDep.artifact(new Action<DependencyArtifact>() {
@Override
public void execute(final DependencyArtifact dependencyArtifact) {
dependencyArtifact.setClassifier("v2");
}
});
final Configuration config = project.getConfigurations().detachedConfiguration(intermediaryDep);
Files.deleteIfExists(tinyMappings);
Files.deleteIfExists(intermediaryJarPath);
Files.copy(
config.getSingleFile().toPath(),
intermediaryJarPath,
StandardCopyOption.REPLACE_EXISTING
);
Files.deleteIfExists(tinyMappings);
} else {
final String url = urlRaw.formatted(encodedMcVersion);
getDownloader().get().apply(url)
.defaultCache()
.downloadPath(intermediaryJarPath);
LOGGER.info("Downloading intermediary from {}", url);
Files.deleteIfExists(tinyMappings);
Files.deleteIfExists(intermediaryJarPath);
getDownloader().get().apply(url)
.defaultCache()
.downloadPath(intermediaryJarPath);
}
MappingConfiguration.extractMappings(intermediaryJarPath, tinyMappings);
}

View File

@@ -0,0 +1,44 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2024 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.providers.mappings;
import java.io.IOException;
import java.nio.file.Path;
import org.gradle.api.Project;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.api.mappings.intermediate.IntermediateMappingsProvider;
@ApiStatus.Internal
public abstract class IntermediateMappingsProviderInternal extends IntermediateMappingsProvider {
public abstract void provide(Path tinyMappings, @Nullable Project project) throws IOException;
@Override
public void provide(Path tinyMappings) throws IOException {
this.provide(tinyMappings, null);
}
}

View File

@@ -61,15 +61,19 @@ public final class IntermediateMappingsService implements SharedService {
final IntermediateMappingsProvider intermediateProvider = extension.getIntermediateMappingsProvider();
final String id = "IntermediateMappingsService:%s:%s".formatted(intermediateProvider.getName(), intermediateProvider.getMinecraftVersion().get());
return sharedServiceManager.getOrCreateService(id, () -> create(intermediateProvider, minecraftProvider));
return sharedServiceManager.getOrCreateService(id, () -> create(intermediateProvider, minecraftProvider, project));
}
@VisibleForTesting
public static IntermediateMappingsService create(IntermediateMappingsProvider intermediateMappingsProvider, MinecraftProvider minecraftProvider) {
public static IntermediateMappingsService create(IntermediateMappingsProvider intermediateMappingsProvider, MinecraftProvider minecraftProvider, Project project) {
final Path intermediaryTiny = minecraftProvider.file(intermediateMappingsProvider.getName() + ".tiny").toPath();
try {
intermediateMappingsProvider.provide(intermediaryTiny);
if (intermediateMappingsProvider instanceof IntermediateMappingsProviderInternal internal) {
internal.provide(intermediaryTiny, project);
} else {
intermediateMappingsProvider.provide(intermediaryTiny);
}
} catch (IOException e) {
try {
Files.deleteIfExists(intermediaryTiny);

View File

@@ -101,4 +101,10 @@ public class LayeredMappingSpecBuilderImpl implements LayeredMappingSpecBuilder
return new LayeredMappingSpec(Collections.unmodifiableList(builtLayers));
}
public static LayeredMappingSpec buildOfficialMojangMappings() {
var builder = new LayeredMappingSpecBuilderImpl(null);
builder.officialMojangMappings();
return builder.build();
}
}

View File

@@ -24,9 +24,9 @@
package net.fabricmc.loom.configuration.providers.mappings;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.io.UncheckedIOException;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@@ -34,20 +34,18 @@ import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.FileCollectionDependency;
import org.gradle.api.artifacts.SelfResolvingDependency;
import org.gradle.api.file.FileCollection;
import org.gradle.api.tasks.TaskDependency;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.api.mappings.layered.MappingContext;
import net.fabricmc.loom.api.mappings.layered.MappingLayer;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.ConfigContext;
import net.fabricmc.loom.configuration.mods.dependency.LocalMavenHelper;
import net.fabricmc.loom.configuration.providers.mappings.extras.unpick.UnpickLayer;
import net.fabricmc.loom.configuration.providers.mappings.utils.AddConstructorMappingVisitor;
import net.fabricmc.loom.util.ZipUtils;
@@ -56,43 +54,61 @@ import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch;
import net.fabricmc.mappingio.format.tiny.Tiny2FileWriter;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
public class LayeredMappingsDependency implements SelfResolvingDependency, FileCollectionDependency {
public record LayeredMappingsFactory(LayeredMappingSpec spec) {
private static final String GROUP = "loom";
private static final String MODULE = "mappings";
private static final Logger LOGGER = LoggerFactory.getLogger(LayeredMappingsFactory.class);
private final Project project;
private final MappingContext mappingContext;
private final LayeredMappingSpec layeredMappingSpec;
private final String version;
public LayeredMappingsDependency(Project project, MappingContext mappingContext, LayeredMappingSpec layeredMappingSpec, String version) {
this.project = project;
this.mappingContext = mappingContext;
this.layeredMappingSpec = layeredMappingSpec;
this.version = version;
}
@Override
public Set<File> resolve() {
Path mappingsDir = mappingContext.minecraftProvider().dir("layered").toPath();
Path mappingsFile = mappingsDir.resolve(String.format("%s.%s-%s.tiny", GROUP, MODULE, getVersion()));
if (!Files.exists(mappingsFile) || mappingContext.refreshDeps()) {
/*
As we no longer have SelfResolvingDependency we now always create the mappings file after evaluation.
This works in a similar way to how remapped mods are handled.
*/
public static void afterEvaluate(ConfigContext configContext) {
for (LayeredMappingsFactory layeredMappingFactory : configContext.extension().getLayeredMappingFactories()) {
try {
var processor = new LayeredMappingsProcessor(layeredMappingSpec);
List<MappingLayer> layers = processor.resolveLayers(mappingContext);
Files.deleteIfExists(mappingsFile);
writeMapping(processor, layers, mappingsFile);
writeSignatureFixes(processor, layers, mappingsFile);
writeUnpickData(processor, layers, mappingsFile);
layeredMappingFactory.evaluate(configContext);
} catch (IOException e) {
throw new RuntimeException("Failed to resolve layered mappings", e);
throw new UncheckedIOException("Failed to setup layered mappings: %s".formatted(layeredMappingFactory.mavenNotation()), e);
}
}
}
return Collections.singleton(mappingsFile.toFile());
private void evaluate(ConfigContext configContext) throws IOException {
LOGGER.info("Evaluating layer mapping: {}", mavenNotation());
final Path mavenRepoDir = configContext.extension().getFiles().getGlobalMinecraftRepo().toPath();
final LocalMavenHelper maven = new LocalMavenHelper(GROUP, MODULE, spec().getVersion(), null, mavenRepoDir);
final Path jar = resolve(configContext.project());
maven.copyToMaven(jar, null);
}
public Path resolve(Project project) throws IOException {
final MappingContext mappingContext = new GradleMappingContext(project, spec.getVersion().replace("+", "_").replace(".", "_"));
final Path mappingsDir = mappingContext.minecraftProvider().dir("layered").toPath();
final Path mappingsZip = mappingsDir.resolve(String.format("%s.%s-%s.jar", GROUP, MODULE, spec.getVersion()));
if (Files.exists(mappingsZip) && !mappingContext.refreshDeps()) {
return mappingsZip;
}
var processor = new LayeredMappingsProcessor(spec);
List<MappingLayer> layers = processor.resolveLayers(mappingContext);
Files.deleteIfExists(mappingsZip);
writeMapping(processor, layers, mappingsZip);
writeSignatureFixes(processor, layers, mappingsZip);
writeUnpickData(processor, layers, mappingsZip);
return mappingsZip;
}
public Dependency createDependency(Project project) {
return project.getDependencies().create(mavenNotation());
}
public String mavenNotation() {
return String.format("%s:%s:%s", GROUP, MODULE, spec.getVersion());
}
private void writeMapping(LayeredMappingsProcessor processor, List<MappingLayer> layers, Path mappingsFile) throws IOException {
@@ -133,57 +149,4 @@ public class LayeredMappingsDependency implements SelfResolvingDependency, FileC
ZipUtils.add(mappingsFile, "extras/definitions.unpick", unpickData.definitions());
ZipUtils.add(mappingsFile, "extras/unpick.json", unpickData.metadata().asJson());
}
@Override
public Set<File> resolve(boolean transitive) {
return resolve();
}
@Override
public TaskDependency getBuildDependencies() {
return task -> Collections.emptySet();
}
@Override
public String getGroup() {
return GROUP;
}
@Override
public String getName() {
return MODULE;
}
@Override
public String getVersion() {
return version;
}
@Override
public boolean contentEquals(Dependency dependency) {
if (dependency instanceof LayeredMappingsDependency layeredMappingsDependency) {
return Objects.equals(layeredMappingsDependency.getVersion(), this.getVersion());
}
return false;
}
@Override
public Dependency copy() {
return new LayeredMappingsDependency(project, mappingContext, layeredMappingSpec, version);
}
@Override
public String getReason() {
return null;
}
@Override
public void because(String s) {
}
@Override
public FileCollection getFiles() {
return project.files(resolve());
}
}

View File

@@ -30,21 +30,37 @@ import java.util.Objects;
import java.util.Set;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.SelfResolvingDependency;
import org.gradle.api.artifacts.FileCollectionDependency;
import net.fabricmc.loom.api.mappings.layered.MappingContext;
import net.fabricmc.loom.api.mappings.layered.spec.FileSpec;
import net.fabricmc.loom.configuration.providers.mappings.GradleMappingContext;
import net.fabricmc.loom.util.gradle.SelfResolvingDependencyUtils;
public record DependencyFileSpec(Dependency dependency) implements FileSpec {
@Override
public Path get(MappingContext context) {
if (dependency instanceof SelfResolvingDependency selfResolvingDependency) {
Set<File> files = selfResolvingDependency.resolve();
if (SelfResolvingDependencyUtils.isExplicitSRD(dependency)) {
if (context instanceof GradleMappingContext gradleMappingContext) {
gradleMappingContext.getExtension().getProblemReporter().reportSelfResolvingDependencyUsage();
}
if (files.size() == 0) {
throw new RuntimeException("SelfResolvingDependency (%s) resolved no files".formatted(selfResolvingDependency.toString()));
Set<File> files = SelfResolvingDependencyUtils.resolve(dependency);
if (files.isEmpty()) {
throw new RuntimeException("SelfResolvingDependency (%s) resolved no files".formatted(dependency.toString()));
} else if (files.size() > 1) {
throw new RuntimeException("SelfResolvingDependency (%s) resolved too many files (%d) only 1 is expected".formatted(selfResolvingDependency.toString(), files.size()));
throw new RuntimeException("SelfResolvingDependency (%s) resolved too many files (%d) only 1 is expected".formatted(dependency.toString(), files.size()));
}
return files.iterator().next().toPath();
} else if (dependency instanceof FileCollectionDependency fileCollectionDependency) {
Set<File> files = fileCollectionDependency.getFiles().getFiles();
if (files.isEmpty()) {
throw new RuntimeException("FileCollectionDependency (%s) resolved no files".formatted(fileCollectionDependency.toString()));
} else if (files.size() > 1) {
throw new RuntimeException("FileCollectionDependency (%s) resolved too many files (%d) only 1 is expected".formatted(fileCollectionDependency.toString(), files.size()));
}
return files.iterator().next().toPath();

View File

@@ -1,7 +1,7 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2021-2022 FabricMC
* Copyright (c) 2021-2024 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
@@ -28,8 +28,10 @@ import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
@@ -69,10 +71,9 @@ import net.fabricmc.loom.configuration.RemapConfigurations;
import net.fabricmc.loom.configuration.ide.RunConfig;
import net.fabricmc.loom.configuration.ide.RunConfigSettings;
import net.fabricmc.loom.configuration.processors.JarProcessor;
import net.fabricmc.loom.configuration.providers.mappings.GradleMappingContext;
import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingSpec;
import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingSpecBuilderImpl;
import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingsDependency;
import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingsFactory;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJarConfiguration;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets;
import net.fabricmc.loom.task.GenerateSourcesTask;
@@ -115,6 +116,10 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
// A common mistake with layered mappings is to call the wrong `officialMojangMappings` method, use this to keep track of when we are building a layered mapping spec.
protected final ThreadLocal<Boolean> layeredSpecBuilderScope = ThreadLocal.withInitial(() -> false);
public static final String DEFAULT_INTERMEDIARY_URL = "https://maven.fabricmc.net/net/fabricmc/intermediary/%1$s/intermediary-%1$s-v2.jar";
protected boolean hasEvaluatedLayeredMappings = false;
protected final Map<LayeredMappingSpec, LayeredMappingsFactory> layeredMappingsDependencyMap = new HashMap<>();
// ===================
// Architectury Loom
@@ -144,7 +149,7 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
.convention(project.provider(() -> !isForgeLike()));
this.modProvidedJavadoc.finalizeValueOnRead();
this.intermediary = project.getObjects().property(String.class)
.convention("https://maven.fabricmc.net/net/fabricmc/intermediary/%1$s/intermediary-%1$s-v2.jar");
.convention(DEFAULT_INTERMEDIARY_URL);
this.intermediateMappingsProvider = project.getObjects().property(IntermediateMappingsProvider.class);
this.intermediateMappingsProvider.finalizeValueOnRead();
@@ -255,14 +260,19 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
@Override
public Dependency layered(Action<LayeredMappingSpecBuilder> action) {
if (hasEvaluatedLayeredMappings) {
throw new IllegalStateException("Layered mappings have already been evaluated");
}
LayeredMappingSpecBuilderImpl builder = new LayeredMappingSpecBuilderImpl(this);
layeredSpecBuilderScope.set(true);
action.execute(builder);
layeredSpecBuilderScope.set(false);
LayeredMappingSpec builtSpec = builder.build();
return new LayeredMappingsDependency(getProject(), new GradleMappingContext(getProject(), builtSpec.getVersion().replace("+", "_").replace(".", "_")), builtSpec, builtSpec.getVersion());
final LayeredMappingSpec builtSpec = builder.build();
final LayeredMappingsFactory layeredMappingsFactory = layeredMappingsDependencyMap.computeIfAbsent(builtSpec, LayeredMappingsFactory::new);
return layeredMappingsFactory.createDependency(getProject());
}
@Override

View File

@@ -1,7 +1,7 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2021 FabricMC
* Copyright (c) 2021-2024 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
@@ -27,6 +27,8 @@ package net.fabricmc.loom.extension;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;
@@ -49,6 +51,7 @@ import net.fabricmc.loom.configuration.accesswidener.AccessWidenerFile;
import net.fabricmc.loom.configuration.providers.forge.DependencyProviders;
import net.fabricmc.loom.configuration.providers.forge.ForgeRunsProvider;
import net.fabricmc.loom.configuration.providers.mappings.IntermediaryMappingsProvider;
import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingsFactory;
import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.library.LibraryProcessorManager;
@@ -81,6 +84,7 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
private boolean refreshDeps;
private Provider<Boolean> multiProjectOptimisation;
private final ListProperty<LibraryProcessorManager.LibraryProcessorFactory> libraryProcessorFactories;
private final LoomProblemReporter problemReporter;
// +-------------------+
// | Architectury Loom |
@@ -118,6 +122,8 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
if (refreshDeps) {
project.getLogger().lifecycle("Refresh dependencies is in use, loom will be significantly slower.");
}
problemReporter = project.getObjects().newInstance(LoomProblemReporter.class);
}
@Override
@@ -293,6 +299,12 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
return remapperExtensions;
}
@Override
public Collection<LayeredMappingsFactory> getLayeredMappingFactories() {
hasEvaluatedLayeredMappings = true;
return Collections.unmodifiableCollection(layeredMappingsDependencyMap.values());
}
@Override
protected <T extends IntermediateMappingsProvider> void configureIntermediateMappingsProviderInternal(T provider) {
provider.getMinecraftVersion().set(getProject().provider(() -> getMinecraftProvider().minecraftVersion()));
@@ -302,6 +314,11 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
provider.getDownloader().disallowChanges();
}
@Override
public LoomProblemReporter getProblemReporter() {
return problemReporter;
}
@Override
public ForgeExtensionAPI getForge() {
ModPlatform.assertPlatform(this, ModPlatform.FORGE);

View File

@@ -0,0 +1,51 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2024 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.extension;
import javax.inject.Inject;
import org.gradle.api.problems.ProblemReporter;
import org.gradle.api.problems.Problems;
import org.gradle.api.problems.Severity;
public abstract class LoomProblemReporter {
private final ProblemReporter problemReporter;
@Inject
public LoomProblemReporter(Problems problems) {
this.problemReporter = problems.forNamespace("net.fabricmc.loom");
}
public void reportSelfResolvingDependencyUsage() {
problemReporter.reporting(spec -> spec
.label("SelfResolvingDependency is deprecated")
.details("SelfResolvingDependency has been deprecated for removal in Gradle 9")
.solution("Please replace usages of SelfResolvingDependency")
.documentedAt("https://github.com/gradle/gradle/pull/27420")
.severity(Severity.WARNING)
.stackLocation()
);
}
}

View File

@@ -43,16 +43,23 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.apache.tools.ant.taskdefs.condition.Os;
import org.gradle.api.Project;
import org.gradle.api.provider.Property;
import org.gradle.api.services.ServiceReference;
import org.gradle.api.tasks.TaskAction;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.configuration.ide.RunConfig;
import net.fabricmc.loom.configuration.ide.RunConfigSettings;
import net.fabricmc.loom.util.gradle.SyncTaskBuildService;
// Recommended vscode plugin pack:
// https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-pack
public class GenVsCodeProjectTask extends AbstractLoomTask {
public abstract class GenVsCodeProjectTask extends AbstractLoomTask {
// Prevent Gradle from running vscode task asynchronously
@ServiceReference(SyncTaskBuildService.NAME)
abstract Property<SyncTaskBuildService> getSyncTask();
@TaskAction
public void genRuns() throws IOException {
clean(getProject());

View File

@@ -26,8 +26,10 @@ package net.fabricmc.loom.task;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.Set;
import com.google.common.collect.ImmutableMap;
@@ -50,8 +52,8 @@ import org.gradle.work.DisableCachingByDefault;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.api.mappings.layered.spec.LayeredMappingSpecBuilder;
import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingsDependency;
import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingSpecBuilderImpl;
import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingsFactory;
import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration;
import net.fabricmc.loom.util.FileSystemUtil;
import net.fabricmc.loom.util.SourceRemapper;
@@ -133,8 +135,8 @@ public abstract class MigrateMappingsTask extends AbstractLoomTask {
throw new UnsupportedOperationException("Migrating Mojang mappings is currently only supported for the specified minecraft version");
}
LayeredMappingsDependency dep = (LayeredMappingsDependency) getExtension().layered(LayeredMappingSpecBuilder::officialMojangMappings);
files = dep.resolve();
LayeredMappingsFactory dep = new LayeredMappingsFactory(LayeredMappingSpecBuilderImpl.buildOfficialMojangMappings());
files = Collections.singleton(dep.resolve(getProject()).toFile());
} else {
Dependency dependency = project.getDependencies().create(mappings);
files = project.getConfigurations().detachedConfiguration(dependency).resolve();
@@ -148,6 +150,8 @@ public abstract class MigrateMappingsTask extends AbstractLoomTask {
project.getLogger().info("Could not locate mappings, presuming V1 Yarn");
files = project.getConfigurations().detachedConfiguration(project.getDependencies().create(ImmutableMap.of("group", "net.fabricmc", "name", "yarn", "version", mappings))).resolve();
}
} catch (IOException e) {
throw new UncheckedIOException("Failed to resolve mappings", e);
}
if (files.isEmpty()) {

View File

@@ -0,0 +1,114 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2024 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.util.gradle;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Set;
import javax.annotation.Nullable;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.FileCollectionDependency;
import org.gradle.api.artifacts.ProjectDependency;
// SelfResolvingDependency is deprecated for removal, use reflection to ensure backwards compat.
@Deprecated
public class SelfResolvingDependencyUtils {
// Set this system prop to disable SRD support before Gradle does.
public static final boolean DISABLE_SRD_SUPPORT = System.getProperty("fabric.loom.disable.srd") != null;
private static final String SELF_RESOLVING_DEPENDENCY_CLASS_NAME = "org.gradle.api.artifacts.SelfResolvingDependency";
@Nullable
private static final Class<?> SELF_RESOLVING_DEPENDENCY_CLASS = getSelfResolvingDependencyOrNull();
@Nullable
private static final Method RESOLVE_METHOD = getResolveMethod(SELF_RESOLVING_DEPENDENCY_CLASS);
/**
* @return true when dependency is a SelfResolvingDependency but NOT a FileCollectionDependency.
*/
public static boolean isExplicitSRD(Dependency dependency) {
// FileCollectionDependency is usually the replacement for SelfResolvingDependency
if (dependency instanceof FileCollectionDependency) {
return false;
} else if (dependency instanceof ProjectDependency) {
return false;
}
return isSRD(dependency);
}
private static boolean isSRD(Dependency dependency) {
if (SELF_RESOLVING_DEPENDENCY_CLASS == null) {
return false;
}
return dependency.getClass().isAssignableFrom(SELF_RESOLVING_DEPENDENCY_CLASS);
}
public static Set<File> resolve(Dependency dependency) {
if (!isSRD(dependency)) {
throw new IllegalStateException("dependency is not a SelfResolvingDependency");
}
try {
return (Set<File>) RESOLVE_METHOD.invoke(dependency);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException("Failed to resolve SelfResolvingDependency", e);
}
}
@Nullable
private static Class<?> getSelfResolvingDependencyOrNull() {
if (DISABLE_SRD_SUPPORT) {
// Lets pretend SRD doesnt exist.
return null;
}
try {
return Class.forName(SELF_RESOLVING_DEPENDENCY_CLASS_NAME);
} catch (ClassNotFoundException e) {
// Gradle 9+
return null;
}
}
@Nullable
private static Method getResolveMethod(Class<?> clazz) {
if (clazz == null) {
// Gradle 9+
return null;
}
try {
var method = clazz.getDeclaredMethod("resolve");
method.setAccessible(true);
return method;
} catch (NoSuchMethodException e) {
throw new IllegalStateException("Failed to get SelfResolvingDependency.resolve() method", e);
}
}
}

View File

@@ -32,16 +32,19 @@ import org.objectweb.asm.commons.Remapper
import org.objectweb.asm.tree.AnnotationNode
import org.slf4j.LoggerFactory
class KotlinClassMetadataRemappingAnnotationVisitor(private val remapper: Remapper, val next: AnnotationVisitor, val className: String?) :
class KotlinClassMetadataRemappingAnnotationVisitor(
private val remapper: Remapper,
val next: AnnotationVisitor,
val className: String?,
) :
AnnotationNode(Opcodes.ASM9, KotlinMetadataRemappingClassVisitor.ANNOTATION_DESCRIPTOR) {
private val logger = LoggerFactory.getLogger(javaClass)
private var _name: String? = null
override fun visit(name: String?, value: Any?) {
override fun visit(
name: String?,
value: Any?,
) {
super.visit(name, value)
this._name = name
}
override fun visitEnd() {
@@ -53,7 +56,11 @@ class KotlinClassMetadataRemappingAnnotationVisitor(private val remapper: Remapp
val currentMinorVersion = KotlinVersion(KotlinVersion.CURRENT.major, KotlinVersion.CURRENT.minor, 0)
if (headerVersion != currentMinorVersion) {
logger.info("Kotlin metadata for class ($className) as it was built using a different major Kotlin version (${header.metadataVersion[0]}.${header.metadataVersion[1]}.x) while the remapper is using (${KotlinVersion.CURRENT}).")
logger.info(
"Kotlin metadata for class ($className) as it was built using a different major Kotlin " +
"version (${header.metadataVersion[0]}.${header.metadataVersion[1]}.x) while the remapper " +
"is using (${KotlinVersion.CURRENT}).",
)
}
when (val metadata = KotlinClassMetadata.readLenient(header)) {
@@ -86,7 +93,13 @@ class KotlinClassMetadataRemappingAnnotationVisitor(private val remapper: Remapp
is KotlinClassMetadata.MultiFileClassPart -> {
var kpackage = metadata.kmPackage
kpackage = KotlinClassRemapper(remapper).remap(kpackage)
val remapped = KotlinClassMetadata.MultiFileClassPart(kpackage, metadata.facadeClassName, metadata.version, metadata.flags).write()
val remapped =
KotlinClassMetadata.MultiFileClassPart(
kpackage,
metadata.facadeClassName,
metadata.version,
metadata.flags,
).write()
writeClassHeader(remapped)
validateKotlinClassHeader(remapped, header)
}
@@ -147,10 +160,17 @@ class KotlinClassMetadataRemappingAnnotationVisitor(private val remapper: Remapp
newNode.accept(next)
}
private fun validateKotlinClassHeader(remapped: Metadata, original: Metadata) {
// This can happen when the remapper is ran on a kotlin version that does not match the version the class was compiled with.
private fun validateKotlinClassHeader(
remapped: Metadata,
original: Metadata,
) {
// This can happen when the remapper is ran on a kotlin version
// that does not match the version the class was compiled with.
if (remapped.data2.size != original.data2.size) {
logger.info("Kotlin class metadata size mismatch: data2 size does not match original in class $className. New: ${remapped.data2.size} Old: ${original.data2.size}")
logger.info(
"Kotlin class metadata size mismatch: data2 size does not match original in class $className. " +
"New: ${remapped.data2.size} Old: ${original.data2.size}",
)
}
}
}

View File

@@ -97,11 +97,12 @@ class KotlinClassRemapper(private val remapper: Remapper) {
}
private fun remap(type: KmType): KmType {
type.classifier = when (val classifier = type.classifier) {
is KmClassifier.Class -> KmClassifier.Class(remap(classifier.name))
is KmClassifier.TypeParameter -> KmClassifier.TypeParameter(classifier.id)
is KmClassifier.TypeAlias -> KmClassifier.TypeAlias(remap(classifier.name))
}
type.classifier =
when (val classifier = type.classifier) {
is KmClassifier.Class -> KmClassifier.Class(remap(classifier.name))
is KmClassifier.TypeParameter -> KmClassifier.TypeParameter(classifier.id)
is KmClassifier.TypeAlias -> KmClassifier.TypeAlias(remap(classifier.name))
}
type.arguments.replaceAll(this::remap)
type.abbreviatedType = type.abbreviatedType?.let { remap(it) }
type.outerType = type.outerType?.let { remap(it) }

View File

@@ -50,7 +50,10 @@ class KotlinMetadataRemappingClassVisitor(private val remapper: Remapper, next:
super.visit(version, access, name, signature, superName, interfaces)
}
override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor? {
override fun visitAnnotation(
descriptor: String,
visible: Boolean,
): AnnotationVisitor? {
var result: AnnotationVisitor? = super.visitAnnotation(descriptor, visible)
if (descriptor == ANNOTATION_DESCRIPTOR && result != null) {

View File

@@ -30,7 +30,10 @@ import net.fabricmc.tinyremapper.api.TrClass
import org.objectweb.asm.ClassVisitor
object KotlinMetadataTinyRemapperExtensionImpl : KotlinMetadataTinyRemapperExtension {
override fun insertApplyVisitor(cls: TrClass, next: ClassVisitor?): ClassVisitor {
override fun insertApplyVisitor(
cls: TrClass,
next: ClassVisitor?,
): ClassVisitor {
return KotlinMetadataRemappingClassVisitor(cls.environment.remapper, next)
}