Merge remote-tracking branch 'upstream/dev/0.12' into dev/0.12.0

# Conflicts:
#	.github/workflows/test.yml
#	bootstrap/test-project/build.gradle
#	build.gradle
#	src/main/java/net/fabricmc/loom/LoomGradlePlugin.java
#	src/main/java/net/fabricmc/loom/LoomRepositoryPlugin.java
#	src/main/java/net/fabricmc/loom/api/MixinExtensionAPI.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/configuration/mods/ModProcessor.java
#	src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java
#	src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftJarConfiguration.java
#	src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftProvider.java
#	src/main/java/net/fabricmc/loom/configuration/providers/minecraft/assets/MinecraftAssetsProvider.java
#	src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/AbstractMappedMinecraftProvider.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/AbstractRunTask.java
#	src/main/java/net/fabricmc/loom/task/RemapJarTask.java
#	src/main/java/net/fabricmc/loom/task/launch/GenerateDLIConfigTask.java
#	src/main/java/net/fabricmc/loom/task/service/TinyRemapperService.java
#	src/main/java/net/fabricmc/loom/util/Constants.java
#	src/main/java/net/fabricmc/loom/util/ModUtils.java
#	src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinMetadataTinyRemapperExtensionImpl.kt
#	src/test/kotlin/net/fabricmc/loom/test/kotlin/KotlinClassMetadataRemappingAnnotationVisitorTest.kt
#	src/test/resources/projects/kotlin/build.gradle.kts
This commit is contained in:
Juuz
2022-05-08 18:36:35 +03:00
102 changed files with 3299 additions and 594 deletions

View File

@@ -129,10 +129,6 @@ public interface LoomGradleExtension extends LoomGradleExtensionAPI {
boolean isRootProject();
default String getIntermediaryUrl(String minecraftVersion) {
return String.format(this.getIntermediaryUrl().get(), minecraftVersion);
}
@Override
MixinExtension getMixin();

View File

@@ -48,6 +48,7 @@ import net.fabricmc.loom.decompilers.DecompilerConfiguration;
import net.fabricmc.loom.extension.LoomFiles;
import net.fabricmc.loom.extension.LoomGradleExtensionImpl;
import net.fabricmc.loom.task.LoomTasks;
import net.fabricmc.loom.util.LibraryLocationLogger;
public class LoomGradlePlugin implements BootstrappedPlugin {
public static boolean refreshDeps;
@@ -73,6 +74,8 @@ public class LoomGradlePlugin implements BootstrappedPlugin {
project.getLogger().lifecycle("Architectury Loom: " + LOOM_VERSION);
}
LibraryLocationLogger.logLibraryVersions();
refreshDeps = project.getGradle().getStartParameter().isRefreshDependencies() || Boolean.getBoolean("loom.refresh");
if (refreshDeps) {

View File

@@ -26,7 +26,9 @@ package net.fabricmc.loom;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.ArtifactRepositoryContainer;
import org.gradle.api.artifacts.dsl.RepositoryHandler;
import org.gradle.api.artifacts.repositories.ArtifactRepository;
import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
import org.gradle.api.initialization.Settings;
@@ -75,7 +77,8 @@ public class LoomRepositoryPlugin implements Plugin<PluginAware> {
repo.setName("Fabric");
repo.setUrl(MirrorUtil.getFabricRepository(target));
});
repositories.maven(repo -> {
MavenArtifactRepository mojangRepo = repositories.maven(repo -> {
repo.setName("Mojang");
repo.setUrl(MirrorUtil.getLibrariesBase(target));
@@ -100,6 +103,16 @@ public class LoomRepositoryPlugin implements Plugin<PluginAware> {
sources.ignoreGradleMetadataRedirection();
});
});
// If a mavenCentral repo is already defined, remove the mojang repo and add it back before the mavenCentral repo so that it will be checked first.
// See: https://github.com/FabricMC/fabric-loom/issues/621
ArtifactRepository mavenCentral = repositories.findByName(ArtifactRepositoryContainer.DEFAULT_MAVEN_CENTRAL_REPO_NAME);
if (mavenCentral != null) {
repositories.remove(mojangRepo);
repositories.add(repositories.indexOf(mavenCentral), mojangRepo);
}
repositories.mavenCentral();
repositories.ivy(repo -> {

View File

@@ -39,11 +39,13 @@ import org.gradle.api.publish.maven.MavenPublication;
import org.jetbrains.annotations.ApiStatus;
import net.fabricmc.loom.api.decompilers.DecompilerOptions;
import net.fabricmc.loom.api.mappings.intermediate.IntermediateMappingsProvider;
import net.fabricmc.loom.api.mappings.layered.spec.LayeredMappingSpecBuilder;
import net.fabricmc.loom.configuration.ide.RunConfig;
import net.fabricmc.loom.configuration.ide.RunConfigSettings;
import net.fabricmc.loom.configuration.launch.LaunchProviderSettings;
import net.fabricmc.loom.configuration.processors.JarProcessor;
import net.fabricmc.loom.configuration.providers.mappings.NoOpIntermediateMappingsProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJarConfiguration;
import net.fabricmc.loom.util.DeprecationHelper;
import net.fabricmc.loom.util.ModPlatform;
@@ -75,9 +77,7 @@ public interface LoomGradleExtensionAPI {
ConfigurableFileCollection getLog4jConfigs();
default Dependency officialMojangMappings() {
return layered(LayeredMappingSpecBuilder::officialMojangMappings);
}
Dependency officialMojangMappings();
Dependency layered(Action<LayeredMappingSpecBuilder> action);
@@ -89,6 +89,16 @@ public interface LoomGradleExtensionAPI {
void mixin(Action<MixinExtensionAPI> action);
/**
* Optionally register and configure a {@link ModSettings} object. The name should match the modid.
* This is generally only required when the mod spans across multiple classpath directories, such as when using split sourcesets.
*/
@ApiStatus.Experimental
void mods(Action<NamedDomainObjectContainer<ModSettings>> action);
@ApiStatus.Experimental
NamedDomainObjectContainer<ModSettings> getMods();
@ApiStatus.Experimental
// TODO: move this from LoomGradleExtensionAPI to LoomGradleExtension once getRefmapName & setRefmapName is removed.
MixinExtensionAPI getMixin();
@@ -137,6 +147,30 @@ public interface LoomGradleExtensionAPI {
*/
Property<Boolean> getEnableTransitiveAccessWideners();
/**
* When true loom will apply mod provided javadoc from dependencies.
*
* @return the property controlling the mod provided javadoc
*/
Property<Boolean> getEnableModProvidedJavadoc();
@ApiStatus.Experimental
IntermediateMappingsProvider getIntermediateMappingsProvider();
@ApiStatus.Experimental
void setIntermediateMappingsProvider(IntermediateMappingsProvider intermediateMappingsProvider);
@ApiStatus.Experimental
<T extends IntermediateMappingsProvider> void setIntermediateMappingsProvider(Class<T> clazz, Action<T> action);
/**
* An Experimental option to provide empty intermediate mappings, to be used for game versions without any intermediate mappings.
*/
@ApiStatus.Experimental
default void noIntermediateMappings() {
setIntermediateMappingsProvider(NoOpIntermediateMappingsProvider.class, p -> { });
}
/**
* Use "%1$s" as a placeholder for the minecraft version.
*
@@ -152,11 +186,22 @@ public interface LoomGradleExtensionAPI {
getMinecraftJarConfiguration().set(MinecraftJarConfiguration.SERVER_ONLY);
}
@ApiStatus.Experimental
default void clientOnlyMinecraftJar() {
getMinecraftJarConfiguration().set(MinecraftJarConfiguration.CLIENT_ONLY);
}
@ApiStatus.Experimental
default void splitMinecraftJar() {
getMinecraftJarConfiguration().set(MinecraftJarConfiguration.SPLIT);
}
@ApiStatus.Experimental
void splitEnvironmentSourceSets();
@ApiStatus.Experimental
boolean areEnvironmentSourceSetsSplit();
Property<Boolean> getRuntimeOnlyLog4j();
// ===================

View File

@@ -36,6 +36,8 @@ public interface MixinExtensionAPI {
Property<String> getDefaultRefmapName();
Property<String> getRefmapTargetNamespace();
Property<String> getLegacyRemapToNamespace();
/**

View File

@@ -0,0 +1,62 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.api;
import javax.inject.Inject;
import org.gradle.api.Named;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.tasks.SourceSet;
import org.jetbrains.annotations.ApiStatus;
/**
* A {@link Named} object for setting mod-related values. The {@linkplain Named#getName() name} should match the mod id.
*/
@ApiStatus.Experimental
public abstract class ModSettings implements Named {
/**
* List of classpath directories, used to populate the `fabric.classPathGroups` Fabric Loader system property.
*/
public abstract ListProperty<SourceSet> getModSourceSets();
/**
* List of classpath directories, or jar files used to populate the `fabric.classPathGroups` Fabric Loader system property.
*/
public abstract ConfigurableFileCollection getModFiles();
@Inject
public ModSettings() {
getModSourceSets().finalizeValueOnRead();
getModFiles().finalizeValueOnRead();
}
/**
* Mark a {@link SourceSet} output directories part of the named mod.
*/
public void sourceSet(SourceSet sourceSet) {
getModSourceSets().add(sourceSet);
}
}

View File

@@ -0,0 +1,47 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.api.mappings.intermediate;
import java.io.IOException;
import java.nio.file.Path;
import org.gradle.api.Named;
import org.gradle.api.provider.Property;
import org.jetbrains.annotations.ApiStatus;
/**
* A simple API to allow 3rd party plugins.
* Implement by creating an abstract class overriding provide and getName
*/
@ApiStatus.Experimental
public abstract class IntermediateMappingsProvider implements Named {
public abstract Property<String> getMinecraftVersion();
/**
* Generate or download a tinyv2 mapping file with intermediary and named namespaces.
* @throws IOException
*/
public abstract void provide(Path tinyMappings) throws IOException;
}

View File

@@ -42,6 +42,7 @@ import org.gradle.api.tasks.SourceSet;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.ide.idea.IdeaUtils;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets;
import net.fabricmc.loom.extension.MixinExtension;
import net.fabricmc.loom.task.service.MixinMappingsService;
import net.fabricmc.loom.util.Constants;
@@ -90,7 +91,7 @@ public abstract class AnnotationProcessorInvoker<T extends Task> {
put(Constants.MixinArguments.IN_MAP_FILE_NAMED_INTERMEDIARY, mappings.toFile().getCanonicalPath());
put(Constants.MixinArguments.OUT_MAP_FILE_NAMED_INTERMEDIARY, MixinMappingsService.getMixinMappingFile(project, sourceSet).getCanonicalPath());
put(Constants.MixinArguments.OUT_REFMAP_FILE, getRefmapDestination(task, refmapName));
put(Constants.MixinArguments.DEFAULT_OBFUSCATION_ENV, "named:intermediary");
put(Constants.MixinArguments.DEFAULT_OBFUSCATION_ENV, "named:" + loom.getMixin().getRefmapTargetNamespace().get());
put(Constants.MixinArguments.QUIET, "true");
}};
@@ -104,15 +105,16 @@ public abstract class AnnotationProcessorInvoker<T extends Task> {
public void configureMixin() {
LoomGradleExtension extension = LoomGradleExtension.get(project);
ConfigurationContainer configs = project.getConfigurations();
MinecraftSourceSets minecraftSourceSets = MinecraftSourceSets.get(project);
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
processorConfig.extendsFrom(
configs.getByName(Constants.Configurations.MINECRAFT_NAMED),
configs.getByName(Constants.Configurations.MOD_COMPILE_CLASSPATH_MAPPED),
configs.getByName(Constants.Configurations.MAPPINGS_FINAL)
configs.getByName(minecraftSourceSets.getCombinedSourceSetName()),
configs.getByName(Constants.Configurations.MOD_COMPILE_CLASSPATH_MAPPED),
configs.getByName(Constants.Configurations.MAPPINGS_FINAL)
);
if (extension.isForge()) {

View File

@@ -27,6 +27,7 @@ package net.fabricmc.loom.configuration;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Set;
import org.gradle.api.NamedDomainObjectProvider;
@@ -48,6 +49,7 @@ import net.fabricmc.loom.configuration.accesstransformer.AccessTransformerJarPro
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.mods.ModJavadocProcessor;
import net.fabricmc.loom.configuration.processors.JarProcessorManager;
import net.fabricmc.loom.configuration.providers.forge.DependencyProviders;
import net.fabricmc.loom.configuration.providers.forge.ForgeProvider;
@@ -60,6 +62,7 @@ import net.fabricmc.loom.configuration.providers.forge.SrgProvider;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJarConfiguration;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.IntermediaryMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.NamedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.SrgMinecraftProvider;
@@ -89,7 +92,6 @@ public final class CompileConfiguration {
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_RUNTIME_DEPENDENCIES, configuration -> configuration.setTransitive(false));
extension.createLazyConfiguration(Constants.Configurations.MINECRAFT_DEPENDENCIES, configuration -> {
@@ -177,13 +179,7 @@ public final class CompileConfiguration {
}
}
extendsFrom(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.MINECRAFT_NAMED, project);
extendsFrom(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.MINECRAFT_NAMED, project);
extendsFrom(JavaPlugin.TEST_COMPILE_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.MINECRAFT_NAMED, project);
extendsFrom(JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.MINECRAFT_NAMED, project);
extendsFrom(Constants.Configurations.LOADER_DEPENDENCIES, Constants.Configurations.MINECRAFT_DEPENDENCIES, project);
extendsFrom(Constants.Configurations.MINECRAFT_NAMED, Constants.Configurations.LOADER_DEPENDENCIES, project);
extendsFrom(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.MAPPINGS_FINAL, project);
extendsFrom(JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.MAPPINGS_FINAL, project);
@@ -215,6 +211,8 @@ public final class CompileConfiguration {
});
p.afterEvaluate(project -> {
MinecraftSourceSets.get(project).afterEvaluate(project);
try {
setupMinecraft(project);
} catch (Exception e) {
@@ -339,6 +337,15 @@ public final class CompileConfiguration {
}
}
if (extension.getEnableModProvidedJavadoc().get()) {
// This doesn't do any processing on the compiled jar, but it does have an effect on the generated sources.
final ModJavadocProcessor javadocProcessor = ModJavadocProcessor.create(project);
if (javadocProcessor != null) {
extension.getGameJarProcessors().add(javadocProcessor);
}
}
if (extension.isForge()) {
Set<File> atFiles = AccessTransformerJarProcessor.getAccessTransformerFiles(project);
@@ -389,7 +396,13 @@ public final class CompileConfiguration {
.apply(project, extension.getNamedMinecraftProvider()).afterEvaluation();
}
private static void extendsFrom(String a, String b, Project project) {
public static void extendsFrom(List<String> parents, String b, Project project) {
for (String parent : parents) {
extendsFrom(parent, b, project);
}
}
public static void extendsFrom(String a, String b, Project project) {
project.getConfigurations().getByName(a, configuration -> configuration.extendsFrom(project.getConfigurations().getByName(b)));
}

View File

@@ -53,6 +53,7 @@ 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.ide.idea.IdeaUtils;
import net.fabricmc.loom.configuration.providers.BundleMetadata;
import net.fabricmc.loom.util.Constants;
@@ -115,16 +116,6 @@ public class RunConfig {
return e;
}
private static String getIdeaModuleName(Project project, SourceSet srcs) {
String module = project.getName() + "." + srcs.getName();
while ((project = project.getParent()) != null) {
module = project.getName() + "." + module;
}
return module;
}
private static void populate(Project project, LoomGradleExtension extension, RunConfig runConfig, String environment) {
runConfig.configName += extension.isRootProject() ? "" : " (" + project.getPath() + ")";
runConfig.eclipseProjectName = project.getExtensions().getByType(EclipseModel.class).getProject().getName();
@@ -183,7 +174,7 @@ public class RunConfig {
runConfig.envVariables.putAll(settings.envVariables);
runConfig.configName = configName;
populate(project, extension, runConfig, environment);
runConfig.ideaModuleName = getIdeaModuleName(project, sourceSet);
runConfig.ideaModuleName = IdeaUtils.getIdeaModuleName(project, sourceSet);
runConfig.runDirIdeaUrl = "file://$PROJECT_DIR$/" + runDir;
runConfig.runDir = runDir;
runConfig.sourceSet = sourceSet;

View File

@@ -26,6 +26,9 @@ package net.fabricmc.loom.configuration.ide.idea;
import java.util.Objects;
import org.gradle.api.Project;
import org.gradle.api.tasks.SourceSet;
public class IdeaUtils {
public static boolean isIdeaSync() {
return Boolean.parseBoolean(System.getProperty("idea.sync.active", "false"));
@@ -42,4 +45,14 @@ public class IdeaUtils {
final int minor = Integer.parseInt(split[1]);
return major > 2021 || (major == 2021 && minor >= 3);
}
public static String getIdeaModuleName(Project project, SourceSet srcs) {
String module = project.getName() + "." + srcs.getName();
while ((project = project.getParent()) != null) {
module = project.getName() + "." + module;
}
return module;
}
}

View File

@@ -63,6 +63,7 @@ import net.fabricmc.loom.configuration.processors.JarProcessor;
import net.fabricmc.loom.task.GenerateSourcesTask;
import net.fabricmc.loom.util.Checksum;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.ModUtils;
import net.fabricmc.loom.util.Pair;
import net.fabricmc.loom.util.TinyRemapperHelper;
import net.fabricmc.loom.util.ZipUtils;
@@ -314,18 +315,12 @@ public class InterfaceInjectionProcessor implements JarProcessor, GenerateSource
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.
* Reads the injected interfaces contained in a mod jar, or returns empty if there is none.
*/
public static List<InjectedInterface> fromModJar(Path modJarPath) {
final byte[] modJsonBytes;
final JsonObject jsonObject = ModUtils.getFabricModJson(modJarPath);
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) {
if (jsonObject == null) {
byte[] commonJsonBytes;
try {
@@ -353,12 +348,9 @@ public class InterfaceInjectionProcessor implements JarProcessor, GenerateSource
}
}
}
return Collections.emptyList();
}
final JsonObject jsonObject = LoomGradlePlugin.GSON.fromJson(new String(modJsonBytes, StandardCharsets.UTF_8), JsonObject.class);
return fromJson(jsonObject, modJarPath.toString());
}
@@ -375,11 +367,11 @@ public class InterfaceInjectionProcessor implements JarProcessor, GenerateSource
final JsonObject custom = jsonObject.getAsJsonObject("custom");
if (!custom.has("loom:injected_interfaces")) {
if (!custom.has(Constants.CustomModJsonKeys.INJECTED_INTERFACE)) {
return Collections.emptyList();
}
final JsonObject addedIfaces = custom.getAsJsonObject("loom:injected_interfaces");
final JsonObject addedIfaces = custom.getAsJsonObject(Constants.CustomModJsonKeys.INJECTED_INTERFACE);
final List<InjectedInterface> result = new ArrayList<>();

View File

@@ -0,0 +1,218 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 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.mods;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import com.google.gson.JsonObject;
import org.gradle.api.Project;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.RemappedConfigurationEntry;
import net.fabricmc.loom.configuration.processors.JarProcessor;
import net.fabricmc.loom.task.GenerateSourcesTask;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.ModUtils;
import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.mappingio.MappingReader;
import net.fabricmc.mappingio.tree.MappingTree;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
public final class ModJavadocProcessor implements JarProcessor, GenerateSourcesTask.MappingsProcessor {
private static final Logger LOGGER = LoggerFactory.getLogger(ModJavadocProcessor.class);
private final List<ModJavadoc> javadocs;
private ModJavadocProcessor(List<ModJavadoc> javadocs) {
this.javadocs = javadocs;
}
@Nullable
public static ModJavadocProcessor create(Project project) {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
final List<ModJavadoc> javadocs = new ArrayList<>();
for (RemappedConfigurationEntry entry : Constants.MOD_COMPILE_ENTRIES) {
Set<File> artifacts = extension.getLazyConfigurationProvider(entry.sourceConfiguration())
.get()
.resolve();
for (File artifact : artifacts) {
if (!ModUtils.isMod(artifact.toPath())) {
continue;
}
final ModJavadoc modJavadoc;
try {
modJavadoc = ModJavadoc.fromModJar(artifact.toPath());
} catch (IOException e) {
throw new UncheckedIOException("Failed to read mod jar (%s)".formatted(artifact), e);
}
if (modJavadoc != null) {
javadocs.add(modJavadoc);
}
}
}
if (javadocs.isEmpty()) {
return null;
}
return new ModJavadocProcessor(javadocs);
}
@Override
public boolean transform(MemoryMappingTree mappings) {
for (ModJavadoc javadoc : javadocs) {
javadoc.apply(mappings);
}
return true;
}
@Override
public String getId() {
return "loom:interface_injection:" + javadocs.hashCode();
}
@Override
public void setup() {
}
@Override
public void process(File file) {
// No need to actually process anything, we need to be a JarProcessor to ensure that the jar is cached correctly.
}
public record ModJavadoc(String modId, MemoryMappingTree mappingTree) {
@Nullable
public static ModJavadoc fromModJar(Path path) throws IOException {
JsonObject jsonObject = ModUtils.getFabricModJson(path);
if (jsonObject == null || !jsonObject.has("custom")) {
return null;
}
final String modId = jsonObject.get("id").getAsString();
final JsonObject custom = jsonObject.getAsJsonObject("custom");
if (!custom.has(Constants.CustomModJsonKeys.PROVIDED_JAVADOC)) {
return null;
}
final String javaDocPath = custom.getAsJsonPrimitive(Constants.CustomModJsonKeys.PROVIDED_JAVADOC).getAsString();
final byte[] data = ZipUtils.unpack(path, javaDocPath);
final MemoryMappingTree mappings = new MemoryMappingTree();
try (Reader reader = new InputStreamReader(new ByteArrayInputStream(data))) {
MappingReader.read(reader, mappings);
}
if (!mappings.getSrcNamespace().equals(MappingsNamespace.INTERMEDIARY.toString())) {
throw new IllegalStateException("Javadoc provided by mod (%s) must be have an intermediary source namespace".formatted(modId));
}
if (!mappings.getDstNamespaces().isEmpty()) {
throw new IllegalStateException("Javadoc provided by mod (%s) must not contain any dst names".formatted(modId));
}
return new ModJavadoc(modId, mappings);
}
public void apply(MemoryMappingTree target) {
if (!mappingTree.getSrcNamespace().equals(target.getSrcNamespace())) {
throw new IllegalStateException("Cannot apply mappings to differing namespaces. source: %s target: %s".formatted(mappingTree.getSrcNamespace(), target.getSrcNamespace()));
}
for (MappingTree.ClassMapping sourceClass : mappingTree.getClasses()) {
final MappingTree.ClassMapping targetClass = target.getClass(sourceClass.getSrcName());
if (targetClass == null) {
LOGGER.warn("Could not find provided javadoc target class {} from mod {}", sourceClass.getSrcName(), modId);
continue;
}
applyComment(sourceClass, targetClass);
for (MappingTree.FieldMapping sourceField : sourceClass.getFields()) {
final MappingTree.FieldMapping targetField = targetClass.getField(sourceField.getSrcName(), sourceField.getSrcDesc());
if (targetField == null) {
LOGGER.warn("Could not find provided javadoc target field {}{} from mod {}", sourceField.getSrcName(), sourceField.getSrcDesc(), modId);
continue;
}
applyComment(sourceField, targetField);
}
for (MappingTree.MethodMapping sourceMethod : sourceClass.getMethods()) {
final MappingTree.MethodMapping targetMethod = targetClass.getMethod(sourceMethod.getSrcName(), sourceMethod.getSrcDesc());
if (targetMethod == null) {
LOGGER.warn("Could not find provided javadoc target method {}{} from mod {}", sourceMethod.getSrcName(), sourceMethod.getSrcDesc(), modId);
continue;
}
applyComment(sourceMethod, targetMethod);
}
}
}
private <T extends MappingTree.ElementMapping> void applyComment(T source, T target) {
String sourceComment = source.getComment();
if (sourceComment == null) {
LOGGER.warn("Mod {} provided javadoc has mapping for {}, without comment", modId, source);
return;
}
String targetComment = target.getComment();
if (targetComment == null) {
targetComment = "";
} else {
targetComment += "\n";
}
targetComment += sourceComment;
target.setComment(targetComment);
}
}
}

View File

@@ -24,6 +24,8 @@
package net.fabricmc.loom.configuration.mods;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
@@ -33,6 +35,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.jar.Manifest;
import com.google.common.base.Stopwatch;
import com.google.gson.JsonObject;
@@ -51,11 +54,13 @@ import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.RemappedConfigurationEntry;
import net.fabricmc.loom.configuration.processors.dependency.ModDependencyInfo;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.kotlin.remapping.KotlinMetadataTinyRemapperExtension;
import net.fabricmc.loom.task.RemapJarTask;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.LoggerFilter;
import net.fabricmc.loom.util.TinyRemapperHelper;
import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.loom.util.kotlin.KotlinClasspathService;
import net.fabricmc.loom.util.kotlin.KotlinRemapperClassloader;
import net.fabricmc.loom.util.srg.AtRemapper;
import net.fabricmc.loom.util.srg.CoreModClassRemapper;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
@@ -159,10 +164,8 @@ public class ModProcessor {
private void remapJars(List<ModDependencyInfo> remapList) throws IOException {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
final MappingsProviderImpl mappingsProvider = extension.getMappingsProvider();
final boolean useKotlinExtension = project.getPluginManager().hasPlugin("org.jetbrains.kotlin.jvm");
String fromM = extension.isForge() ? MappingsNamespace.SRG.toString() : MappingsNamespace.INTERMEDIARY.toString();
String toM = MappingsNamespace.NAMED.toString();
Path[] mcDeps = project.getConfigurations().getByName(Constants.Configurations.LOADER_DEPENDENCIES).getFiles()
.stream().map(File::toPath).toArray(Path[]::new);
@@ -177,8 +180,12 @@ public class ModProcessor {
.withMappings(TinyRemapperHelper.create(mappings, fromM, toM, false))
.renameInvalidLocals(false);
if (useKotlinExtension) {
builder.extension(KotlinMetadataTinyRemapperExtension.INSTANCE);
final KotlinClasspathService kotlinClasspathService = KotlinClasspathService.getOrCreateIfRequired(project);
KotlinRemapperClassloader kotlinRemapperClassloader = null;
if (kotlinClasspathService != null) {
kotlinRemapperClassloader = KotlinRemapperClassloader.create(kotlinClasspathService);
builder.extension(kotlinRemapperClassloader.getTinyRemapperExtension());
}
final TinyRemapper remapper = builder.build();
@@ -236,6 +243,10 @@ public class ModProcessor {
}
} finally {
remapper.finish();
if (kotlinRemapperClassloader != null) {
kotlinRemapperClassloader.close();
}
}
project.getLogger().lifecycle(":remapped " + remapList.size() + " mods (TinyRemapper, " + fromM + " -> " + toM + ") in " + stopwatch.stop());
@@ -250,6 +261,7 @@ public class ModProcessor {
}
stripNestedJars(info.getRemappedOutput());
remapJarManifestEntries(info.getRemappedOutput().toPath());
if (extension.isForge()) {
AtRemapper.remap(project.getLogger(), info.getRemappedOutput().toPath(), mappings);
@@ -259,4 +271,16 @@ public class ModProcessor {
info.finaliseRemapping();
}
}
private void remapJarManifestEntries(Path jar) throws IOException {
ZipUtils.transform(jar, Map.of(RemapJarTask.MANIFEST_PATH, bytes -> {
var manifest = new Manifest(new ByteArrayInputStream(bytes));
manifest.getMainAttributes().putValue(RemapJarTask.MANIFEST_NAMESPACE_KEY, toM);
ByteArrayOutputStream out = new ByteArrayOutputStream();
manifest.write(out);
return out.toByteArray();
}));
}
}

View File

@@ -65,7 +65,7 @@ public class GradleMappingContext implements MappingContext {
@Override
public Supplier<MemoryMappingTree> intermediaryTree() {
return () -> IntermediaryService.getInstance(project, minecraftProvider()).getMemoryMappingTree();
return () -> IntermediateMappingsService.getInstance(project, minecraftProvider()).getMemoryMappingTree();
}
@Override
@@ -82,4 +82,12 @@ public class GradleMappingContext implements MappingContext {
public Logger getLogger() {
return project.getLogger();
}
public Project getProject() {
return project;
}
public LoomGradleExtension getExtension() {
return extension;
}
}

View File

@@ -0,0 +1,71 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 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.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import com.google.common.net.UrlEscapers;
import org.gradle.api.provider.Property;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.api.mappings.intermediate.IntermediateMappingsProvider;
import net.fabricmc.loom.util.DownloadUtil;
public abstract class IntermediaryMappingsProvider extends IntermediateMappingsProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(IntermediateMappingsProvider.class);
public abstract Property<String> getIntermediaryUrl();
@Override
public void provide(Path tinyMappings) throws IOException {
if (Files.exists(tinyMappings) && !LoomGradlePlugin.refreshDeps) {
return;
}
// Download and extract intermediary
final Path intermediaryJarPath = Files.createTempFile(getName(), ".jar");
final String encodedMcVersion = UrlEscapers.urlFragmentEscaper().escape(getMinecraftVersion().get());
final URL url = new URL(getIntermediaryUrl().get().formatted(encodedMcVersion));
LOGGER.info("Downloading intermediary from {}", url);
Files.deleteIfExists(tinyMappings);
Files.deleteIfExists(intermediaryJarPath);
DownloadUtil.downloadIfChanged(url, intermediaryJarPath.toFile(), LOGGER);
MappingsProviderImpl.extractMappings(intermediaryJarPath, tinyMappings);
}
@Override
public @NotNull String getName() {
return "intermediary-v2";
}
}

View File

@@ -25,10 +25,8 @@
package net.fabricmc.loom.configuration.providers.mappings;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -37,59 +35,52 @@ import java.util.Objects;
import java.util.function.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.net.UrlEscapers;
import org.gradle.api.Project;
import org.jetbrains.annotations.VisibleForTesting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.api.mappings.intermediate.IntermediateMappingsProvider;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.util.DownloadUtil;
import net.fabricmc.loom.util.service.SharedService;
import net.fabricmc.loom.util.service.SharedServiceManager;
import net.fabricmc.mappingio.adapter.MappingNsCompleter;
import net.fabricmc.mappingio.format.Tiny2Reader;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
public final class IntermediaryService implements SharedService {
private static final Logger LOGGER = LoggerFactory.getLogger(IntermediaryService.class);
public final class IntermediateMappingsService implements SharedService {
private final Path intermediaryTiny;
private final Supplier<MemoryMappingTree> memoryMappingTree = Suppliers.memoize(this::createMemoryMappingTree);
private IntermediaryService(Path intermediaryTiny) {
private IntermediateMappingsService(Path intermediaryTiny) {
this.intermediaryTiny = intermediaryTiny;
}
public static synchronized IntermediaryService getInstance(Project project, MinecraftProvider minecraftProvider) {
public static synchronized IntermediateMappingsService getInstance(Project project, MinecraftProvider minecraftProvider) {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
final String encodedMinecraftVersion = UrlEscapers.urlFragmentEscaper().escape(minecraftProvider.minecraftVersion());
final String intermediaryArtifactUrl = extension.getIntermediaryUrl(encodedMinecraftVersion);
final IntermediateMappingsProvider intermediateProvider = extension.getIntermediateMappingsProvider();
final String id = "IntermediateMappingsService:%s:%s".formatted(intermediateProvider.getName(), intermediateProvider.getMinecraftVersion().get());
return SharedServiceManager.get(project).getOrCreateService("IntermediaryService:" + intermediaryArtifactUrl,
() -> create(intermediaryArtifactUrl, minecraftProvider));
return SharedServiceManager.get(project).getOrCreateService(id, () -> create(intermediateProvider, minecraftProvider));
}
@VisibleForTesting
public static IntermediaryService create(String intermediaryUrl, MinecraftProvider minecraftProvider) {
final Path intermediaryTiny = minecraftProvider.file("intermediary-v2.tiny").toPath();
if (!Files.exists(intermediaryTiny) || LoomGradlePlugin.refreshDeps) {
// Download and extract intermediary
File intermediaryJar = minecraftProvider.file("intermediary-v2.jar");
public static IntermediateMappingsService create(IntermediateMappingsProvider intermediateMappingsProvider, MinecraftProvider minecraftProvider) {
final Path intermediaryTiny = minecraftProvider.file(intermediateMappingsProvider.getName() + ".tiny").toPath();
try {
intermediateMappingsProvider.provide(intermediaryTiny);
} catch (IOException e) {
try {
DownloadUtil.downloadIfChanged(new URL(intermediaryUrl), intermediaryJar, LOGGER);
MappingsProviderImpl.extractMappings(intermediaryJar.toPath(), intermediaryTiny);
} catch (IOException e) {
throw new UncheckedIOException("Failed to download and extract intermediary", e);
Files.deleteIfExists(intermediaryTiny);
} catch (IOException ex) {
ex.printStackTrace();
}
throw new UncheckedIOException("Failed to provide intermediate mappings", e);
}
return new IntermediaryService(intermediaryTiny);
return new IntermediateMappingsService(intermediaryTiny);
}
private MemoryMappingTree createMemoryMappingTree() {

View File

@@ -102,9 +102,9 @@ public class MappingsProviderImpl implements MappingsProvider, SharedService {
private UnpickMetadata unpickMetadata;
private Map<String, String> signatureFixes;
private final Supplier<IntermediaryService> intermediaryService;
private final Supplier<IntermediateMappingsService> intermediaryService;
protected MappingsProviderImpl(String mappingsIdentifier, Path mappingsWorkingDir, Supplier<IntermediaryService> intermediaryService) {
protected MappingsProviderImpl(String mappingsIdentifier, Path mappingsWorkingDir, Supplier<IntermediateMappingsService> intermediaryService) {
this.mappingsIdentifier = mappingsIdentifier;
this.mappingsWorkingDir = mappingsWorkingDir;
@@ -121,7 +121,7 @@ public class MappingsProviderImpl implements MappingsProvider, SharedService {
public static synchronized MappingsProviderImpl getInstance(Project project, DependencyInfo dependency, MinecraftProvider minecraftProvider) {
return SharedServiceManager.get(project).getOrCreateService("MappingsProvider:%s:%s".formatted(dependency.getDepString(), minecraftProvider.minecraftVersion()), () -> {
Supplier<IntermediaryService> intermediaryService = Suppliers.memoize(() -> IntermediaryService.getInstance(project, minecraftProvider));
Supplier<IntermediateMappingsService> intermediaryService = Suppliers.memoize(() -> IntermediateMappingsService.getInstance(project, minecraftProvider));
return create(project, dependency, minecraftProvider, intermediaryService);
});
}
@@ -134,7 +134,7 @@ public class MappingsProviderImpl implements MappingsProvider, SharedService {
return Objects.requireNonNull(mappingTreeWithSrg, "Cannot get mappings before they have been read").get();
}
private static MappingsProviderImpl create(Project project, DependencyInfo dependency, MinecraftProvider minecraftProvider, Supplier<IntermediaryService> intermediaryService) {
private static MappingsProviderImpl create(Project project, DependencyInfo dependency, MinecraftProvider minecraftProvider, Supplier<IntermediateMappingsService> intermediaryService) {
final String version = dependency.getResolvedVersion();
final Path inputJar = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve mappings: " + dependency)).toPath();
final String mappingsName = StringUtils.removeSuffix(dependency.getDependency().getGroup() + "." + dependency.getDependency().getName(), "-unmerged");

View File

@@ -0,0 +1,51 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 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.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import org.jetbrains.annotations.NotNull;
import net.fabricmc.loom.api.mappings.intermediate.IntermediateMappingsProvider;
/**
* A bit of a hack, creates an empty intermediary mapping file to be used for mc versions without any intermediate mappings.
*/
public abstract class NoOpIntermediateMappingsProvider extends IntermediateMappingsProvider {
private static final String HEADER = "tiny\t2\t0\tofficial\tintermediary";
@Override
public void provide(Path tinyMappings) throws IOException {
Files.writeString(tinyMappings, HEADER, StandardCharsets.UTF_8);
}
@Override
public @NotNull String getName() {
return "empty-intermediate";
}
}

View File

@@ -38,7 +38,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.providers.mappings.IntermediaryService;
import net.fabricmc.loom.configuration.providers.mappings.IntermediateMappingsService;
import net.fabricmc.mappingio.adapter.MappingNsCompleter;
import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch;
import net.fabricmc.mappingio.format.Tiny2Reader;
@@ -49,12 +49,12 @@ import net.fabricmc.mappingio.tree.MemoryMappingTree;
public final class MappingsMerger {
private static final Logger LOGGER = LoggerFactory.getLogger(MappingsMerger.class);
public static void mergeAndSaveMappings(Path from, Path out, IntermediaryService intermediaryService) throws IOException {
public static void mergeAndSaveMappings(Path from, Path out, IntermediateMappingsService intermediateMappingsService) throws IOException {
Stopwatch stopwatch = Stopwatch.createStarted();
LOGGER.info(":merging mappings");
MemoryMappingTree intermediaryTree = new MemoryMappingTree();
intermediaryService.getMemoryMappingTree().accept(new MappingSourceNsSwitch(intermediaryTree, MappingsNamespace.INTERMEDIARY.toString()));
intermediateMappingsService.getMemoryMappingTree().accept(new MappingSourceNsSwitch(intermediaryTree, MappingsNamespace.INTERMEDIARY.toString()));
try (BufferedReader reader = Files.newBufferedReader(from, StandardCharsets.UTF_8)) {
Tiny2Reader.read(reader, intermediaryTree);

View File

@@ -37,7 +37,7 @@ import net.fabricmc.loom.util.Architecture;
import net.fabricmc.loom.util.OperatingSystem;
public class LWJGLVersionOverride {
public static final String LWJGL_VERSION = "3.3.0";
public static final String LWJGL_VERSION = "3.3.1";
@Nullable
public static final String NATIVE_CLASSIFIER = getNativesClassifier();
@@ -66,9 +66,15 @@ public class LWJGLVersionOverride {
return false;
}
return versionMeta.libraries().stream()
.map(MinecraftVersionMeta.Library::name)
.anyMatch(s -> s.startsWith("org.lwjgl:lwjgl:3"));
boolean supportedLwjglVersion = versionMeta.libraries().stream()
.anyMatch(library -> library.name().startsWith("org.lwjgl:lwjgl:3"));
boolean hasExistingNatives = versionMeta.libraries().stream()
.filter(library -> library.name().startsWith("org.lwjgl:lwjgl"))
.anyMatch(MinecraftVersionMeta.Library::hasNativesForOS);
// Is LWJGL 3, and doesn't have any existing compatible LWGL natives.
return supportedLwjglVersion && !hasExistingNatives;
}
public static boolean forceOverride(Project project) {

View File

@@ -58,6 +58,10 @@ public class MergedMinecraftProvider extends MinecraftProvider {
public void provide() throws Exception {
super.provide();
if (!getVersionInfo().isVersionOrNewer("2012-07-25T22:00:00+00:00" /* 1.3 release date */)) {
throw new UnsupportedOperationException("Minecraft versions 1.2.5 and older cannot be merged. Please use `loom { server/clientOnlyMinecraftJar() }`");
}
if (!Files.exists(minecraftMergedJar) || isRefreshDeps()) {
try {
mergeJars();

View File

@@ -52,14 +52,22 @@ public enum MinecraftJarConfiguration {
List.of("client", "server")
),
SERVER_ONLY(
ServerOnlyMinecraftProvider::new,
IntermediaryMinecraftProvider.ServerOnlyImpl::new,
NamedMinecraftProvider.ServerOnlyImpl::new,
SingleJarMinecraftProvider::server,
IntermediaryMinecraftProvider.SingleJarImpl::server,
NamedMinecraftProvider.SingleJarImpl::server,
SrgMinecraftProvider.ServerOnlyImpl::new,
ProcessedNamedMinecraftProvider.ServerOnlyImpl::new,
ProcessedNamedMinecraftProvider.SingleJarImpl::server,
SingleJarDecompileConfiguration::new,
List.of("server")
),
CLIENT_ONLY(
SingleJarMinecraftProvider::client,
IntermediaryMinecraftProvider.SingleJarImpl::client,
NamedMinecraftProvider.SingleJarImpl::client,
ProcessedNamedMinecraftProvider.SingleJarImpl::client,
SingleJarDecompileConfiguration::new,
List.of("client")
),
SPLIT(
SplitMinecraftProvider::new,
IntermediaryMinecraftProvider.SplitImpl::new,

View File

@@ -60,8 +60,11 @@ public class MinecraftLibraryProvider {
if (library.isValidForOS() && !library.hasNatives() && library.artifact() != null) {
// 1.4.7 contains an LWJGL version with an invalid maven pom, set the metadata sources to not use the pom for this version.
if ("org.lwjgl.lwjgl:lwjgl:2.9.1-nightly-20130708-debug3".equals(library.name())) {
if ("org.lwjgl.lwjgl:lwjgl:2.9.1-nightly-20130708-debug3".equals(library.name()) || "org.lwjgl.lwjgl:lwjgl:2.9.1-nightly-20131017".equals(library.name())) {
LoomRepositoryPlugin.setupForLegacyVersions(project);
} else if (library.name().startsWith("org.ow2.asm:asm-all")) {
// Don't want asm-all, use the modern split version.
continue;
}
if (runtimeOnlyLog4j && library.name().startsWith("org.apache.logging.log4j")) {

View File

@@ -34,6 +34,7 @@ import java.util.List;
import java.util.Objects;
import java.util.Optional;
import com.google.common.base.Preconditions;
import com.google.common.io.Files;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
@@ -75,6 +76,14 @@ public abstract class MinecraftProvider {
this.project = project;
}
protected boolean provideClient() {
return true;
}
protected boolean provideServer() {
return true;
}
public void provide() throws Exception {
final DependencyInfo dependency = DependencyInfo.create(getProject(), Constants.Configurations.MINECRAFT);
minecraftVersion = dependency.getDependency().getVersion();
@@ -94,7 +103,17 @@ public abstract class MinecraftProvider {
}
if (offline) {
if (minecraftClientJar.exists() && minecraftServerJar.exists()) {
boolean exists = true;
if (provideServer() && !minecraftServerJar.exists()) {
exists = false;
}
if (provideClient() && !minecraftClientJar.exists()) {
exists = false;
}
if (exists) {
getProject().getLogger().debug("Found client and server jars, presuming up-to-date");
} else {
throw new GradleException("Missing jar(s); Client: " + minecraftClientJar.exists() + ", Server: " + minecraftServerJar.exists());
@@ -103,7 +122,9 @@ public abstract class MinecraftProvider {
downloadJars(getProject().getLogger());
}
serverBundleMetadata = BundleMetadata.fromJar(minecraftServerJar.toPath());
if (provideServer()) {
serverBundleMetadata = BundleMetadata.fromJar(minecraftServerJar.toPath());
}
libraryProvider = new MinecraftLibraryProvider();
libraryProvider.provide(this, getProject());
@@ -119,11 +140,17 @@ public abstract class MinecraftProvider {
workingDir = new File(getExtension().getFiles().getUserCache(), minecraftVersion);
workingDir.mkdirs();
minecraftJson = file("minecraft-info.json");
minecraftClientJar = file("minecraft-client.jar");
minecraftServerJar = file("minecraft-server.jar");
minecraftExtractedServerJar = file("minecraft-extracted_server.jar");
versionManifestJson = new File(getExtension().getFiles().getUserCache(), "version_manifest.json");
experimentalVersionsJson = new File(getExtension().getFiles().getUserCache(), "experimental_version_manifest.json");
if (provideClient()) {
minecraftClientJar = file("minecraft-client.jar");
}
if (provideServer()) {
minecraftServerJar = file("minecraft-server.jar");
minecraftExtractedServerJar = file("minecraft-extracted_server.jar");
}
}
private void downloadMcJson(boolean offline) throws IOException {
@@ -243,14 +270,19 @@ public abstract class MinecraftProvider {
return;
}
MinecraftVersionMeta.Download client = versionInfo.download("client");
MinecraftVersionMeta.Download server = versionInfo.download("server");
if (provideClient()) {
MinecraftVersionMeta.Download client = versionInfo.download("client");
HashedDownloadUtil.downloadIfInvalid(new URL(client.url()), minecraftClientJar, client.sha1(), logger, false);
}
HashedDownloadUtil.downloadIfInvalid(new URL(client.url()), minecraftClientJar, client.sha1(), logger, false);
HashedDownloadUtil.downloadIfInvalid(new URL(server.url()), minecraftServerJar, server.sha1(), logger, false);
if (provideServer()) {
MinecraftVersionMeta.Download server = versionInfo.download("server");
HashedDownloadUtil.downloadIfInvalid(new URL(server.url()), minecraftServerJar, server.sha1(), logger, false);
}
}
protected final void extractBundledServerJar() throws IOException {
Preconditions.checkArgument(provideServer(), "Not configured to provide server jar");
Objects.requireNonNull(getServerBundleMetadata(), "Cannot bundled mc jar from none bundled server jar");
getLogger().info(":Extracting server jar from bootstrap");
@@ -281,17 +313,20 @@ public abstract class MinecraftProvider {
}
public File getMinecraftClientJar() {
Preconditions.checkArgument(provideClient(), "Not configured to provide client jar");
return minecraftClientJar;
}
// May be null on older versions
@Nullable
public File getMinecraftExtractedServerJar() {
Preconditions.checkArgument(provideServer(), "Not configured to provide server jar");
return minecraftExtractedServerJar;
}
// This may be the server bundler jar on newer versions prob not what you want.
public File getMinecraftServerJar() {
Preconditions.checkArgument(provideServer(), "Not configured to provide server jar");
return minecraftServerJar;
}
@@ -303,10 +338,6 @@ public abstract class MinecraftProvider {
return versionInfo;
}
public MinecraftLibraryProvider getLibraryProvider() {
return libraryProvider;
}
public String getJarPrefix() {
return jarPrefix;
}
@@ -315,10 +346,6 @@ public abstract class MinecraftProvider {
this.jarPrefix = jarSuffix;
}
public String getTargetConfig() {
return Constants.Configurations.MINECRAFT;
}
@Nullable
public BundleMetadata getServerBundleMetadata() {
return serverBundleMetadata;

View File

@@ -0,0 +1,230 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 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.configuration.CompileConfiguration.extendsFrom;
import java.util.List;
import java.util.function.BiConsumer;
import com.google.common.base.Preconditions;
import org.gradle.api.Project;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.tasks.SourceSet;
import org.gradle.jvm.tasks.Jar;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.util.Constants;
public abstract sealed class MinecraftSourceSets permits MinecraftSourceSets.Single, MinecraftSourceSets.Split {
public static MinecraftSourceSets get(Project project) {
return LoomGradleExtension.get(project).areEnvironmentSourceSetsSplit() ? Split.INSTANCE : Single.INSTANCE;
}
public abstract void applyDependencies(BiConsumer<String, String> consumer, List<String> targets);
public abstract String getCombinedSourceSetName();
public abstract String getSourceSetForEnv(String env);
protected abstract List<String> getAllSourceSetNames();
public void evaluateSplit(Project project) {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
Preconditions.checkArgument(extension.areEnvironmentSourceSetsSplit());
Split.INSTANCE.evaluate(project);
}
public abstract void afterEvaluate(Project project);
protected void createSourceSets(Project project) {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
for (String name : getAllSourceSetNames()) {
extension.createLazyConfiguration(name, configuration -> configuration.setTransitive(false));
// All the configurations extend the loader deps.
extendsFrom(name, Constants.Configurations.LOADER_DEPENDENCIES, project);
}
}
/**
* Used when we have a single source set, either with split or merged jars.
*/
public static final class Single extends MinecraftSourceSets {
private static final String MINECRAFT_NAMED = "minecraftNamed";
private static final Single INSTANCE = new Single();
@Override
public void applyDependencies(BiConsumer<String, String> consumer, List<String> targets) {
for (String target : targets) {
consumer.accept(MINECRAFT_NAMED, target);
}
}
@Override
public String getCombinedSourceSetName() {
return MINECRAFT_NAMED;
}
@Override
public String getSourceSetForEnv(String env) {
return SourceSet.MAIN_SOURCE_SET_NAME;
}
@Override
protected List<String> getAllSourceSetNames() {
return List.of(MINECRAFT_NAMED);
}
@Override
public void afterEvaluate(Project project) {
// This is done in afterEvaluate as we need to be sure that split source sets was not enabled.
createSourceSets(project);
// Default compile and runtime sourcesets.
extendsFrom(List.of(
JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME,
JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME,
JavaPlugin.TEST_COMPILE_CLASSPATH_CONFIGURATION_NAME,
JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME),
MINECRAFT_NAMED, project
);
}
}
/**
* Used when we have a split client/common source set and split jars.
*/
public static final class Split extends MinecraftSourceSets {
private static final String MINECRAFT_COMMON_NAMED = "minecraftCommonNamed";
private static final String MINECRAFT_CLIENT_ONLY_NAMED = "minecraftClientOnlyNamed";
private static final String MINECRAFT_COMBINED_NAMED = "minecraftCombinedNamed";
private static final String CLIENT_ONLY_SOURCE_SET_NAME = "client";
private static final Split INSTANCE = new Split();
@Override
public void applyDependencies(BiConsumer<String, String> consumer, List<String> targets) {
Preconditions.checkArgument(targets.size() == 2);
Preconditions.checkArgument(targets.contains("common"));
Preconditions.checkArgument(targets.contains("clientOnly"));
consumer.accept(MINECRAFT_COMMON_NAMED, "common");
consumer.accept(MINECRAFT_CLIENT_ONLY_NAMED, "clientOnly");
}
@Override
public String getCombinedSourceSetName() {
return MINECRAFT_COMBINED_NAMED;
}
@Override
public String getSourceSetForEnv(String env) {
return env.equals("client") ? CLIENT_ONLY_SOURCE_SET_NAME : SourceSet.MAIN_SOURCE_SET_NAME;
}
@Override
protected List<String> getAllSourceSetNames() {
return List.of(MINECRAFT_COMMON_NAMED, MINECRAFT_CLIENT_ONLY_NAMED, MINECRAFT_COMBINED_NAMED);
}
// Called during evaluation, when the loom extension method is called.
private void evaluate(Project project) {
createSourceSets(project);
// Combined extends from the 2 environments.
extendsFrom(MINECRAFT_COMBINED_NAMED, MINECRAFT_COMMON_NAMED, project);
extendsFrom(MINECRAFT_COMBINED_NAMED, MINECRAFT_CLIENT_ONLY_NAMED, project);
final JavaPluginExtension javaExtension = project.getExtensions().getByType(JavaPluginExtension.class);
final LoomGradleExtension loomExtension = LoomGradleExtension.get(project);
// Register our new client only source set, main becomes common only, with their respective jars.
SourceSet mainSourceSet = javaExtension.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME);
SourceSet clientOnlySourceSet = javaExtension.getSourceSets().create(CLIENT_ONLY_SOURCE_SET_NAME);
extendsFrom(List.of(
mainSourceSet.getCompileClasspathConfigurationName(),
mainSourceSet.getRuntimeClasspathConfigurationName()
), MINECRAFT_COMMON_NAMED, project
);
extendsFrom(List.of(
clientOnlySourceSet.getCompileClasspathConfigurationName(),
clientOnlySourceSet.getRuntimeClasspathConfigurationName()
), MINECRAFT_CLIENT_ONLY_NAMED, project
);
// Client depends on common.
extendsFrom(MINECRAFT_CLIENT_ONLY_NAMED, MINECRAFT_COMMON_NAMED, project);
clientOnlySourceSet.setCompileClasspath(
clientOnlySourceSet.getCompileClasspath()
.plus(mainSourceSet.getCompileClasspath())
.plus(mainSourceSet.getOutput())
);
clientOnlySourceSet.setRuntimeClasspath(
clientOnlySourceSet.getRuntimeClasspath()
.plus(mainSourceSet.getRuntimeClasspath())
.plus(mainSourceSet.getOutput())
);
loomExtension.mixin(mixinExtension -> {
// Generate a refmap for mixins in the new source set.
mixinExtension.add(clientOnlySourceSet, "client-" + mixinExtension.getDefaultRefmapName().get(), (p) -> { });
});
// Include the client only output in the jars
project.getTasks().named(mainSourceSet.getJarTaskName(), Jar.class).configure(jar -> {
jar.from(clientOnlySourceSet.getOutput().getClassesDirs());
jar.from(clientOnlySourceSet.getOutput().getResourcesDir());
});
if (project.getTasks().findByName(mainSourceSet.getSourcesJarTaskName()) == null) {
// No sources.
return;
}
project.getTasks().named(mainSourceSet.getSourcesJarTaskName(), Jar.class).configure(jar -> {
jar.from(clientOnlySourceSet.getAllSource());
});
}
@Override
public void afterEvaluate(Project project) {
}
public static SourceSet getClientSourceSet(Project project) {
Preconditions.checkArgument(LoomGradleExtension.get(project).areEnvironmentSourceSetsSplit());
final JavaPluginExtension javaExtension = project.getExtensions().getByType(JavaPluginExtension.class);
return javaExtension.getSourceSets().getByName(CLIENT_ONLY_SOURCE_SET_NAME);
}
}
}

View File

@@ -29,6 +29,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import net.fabricmc.loom.util.Architecture;
import net.fabricmc.loom.util.OperatingSystem;
@SuppressWarnings("unused")
@@ -85,7 +86,7 @@ public record MinecraftVersionMeta(
return false;
}
if (natives.get(OperatingSystem.CURRENT_OS) == null) {
if (classifierForOS() == null) {
return false;
}
@@ -93,7 +94,13 @@ public record MinecraftVersionMeta(
}
public Download classifierForOS() {
return downloads().classifier(natives.get(OperatingSystem.CURRENT_OS));
String classifier = natives.get(OperatingSystem.CURRENT_OS);
if (Architecture.CURRENT.isArm()) {
classifier += "-arm64";
}
return downloads().classifier(classifier);
}
public Download artifact() {

View File

@@ -35,60 +35,64 @@ import org.gradle.api.Project;
import net.fabricmc.loom.configuration.providers.BundleMetadata;
public final class ServerOnlyMinecraftProvider extends MinecraftProvider {
private Path minecraftServerOnlyJar;
public final class SingleJarMinecraftProvider extends MinecraftProvider {
private final Environment environment;
public ServerOnlyMinecraftProvider(Project project) {
private Path minecraftEnvOnlyJar;
private SingleJarMinecraftProvider(Project project, Environment environment) {
super(project);
this.environment = environment;
}
public static SingleJarMinecraftProvider server(Project project) {
return new SingleJarMinecraftProvider(project, new Server());
}
public static SingleJarMinecraftProvider client(Project project) {
return new SingleJarMinecraftProvider(project, new Client());
}
@Override
protected void initFiles() {
super.initFiles();
minecraftServerOnlyJar = path("minecraft-server-only.jar");
minecraftEnvOnlyJar = path("minecraft-%s-only.jar".formatted(environment.name()));
}
@Override
public List<Path> getMinecraftJars() {
return List.of(minecraftServerOnlyJar);
return List.of(minecraftEnvOnlyJar);
}
@Override
public void provide() throws Exception {
super.provide();
boolean requiresRefresh = isRefreshDeps() || Files.notExists(minecraftServerOnlyJar);
boolean requiresRefresh = isRefreshDeps() || Files.notExists(minecraftEnvOnlyJar);
if (!requiresRefresh) {
return;
}
BundleMetadata serverBundleMetadata = getServerBundleMetadata();
if (serverBundleMetadata == null) {
throw new UnsupportedOperationException("Only Minecraft versions using a bundled server jar support server only configuration, please use a merged jar setup for this version of minecraft");
}
extractBundledServerJar();
final Path serverJar = getMinecraftExtractedServerJar().toPath();
final Path inputJar = environment.getInputJar(this);
TinyRemapper remapper = null;
try {
remapper = TinyRemapper.newRemapper().build();
Files.deleteIfExists(minecraftServerOnlyJar);
Files.deleteIfExists(minecraftEnvOnlyJar);
// Pass through tiny remapper to fix the meta-inf
try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(minecraftServerOnlyJar).build()) {
outputConsumer.addNonClassFiles(serverJar, NonClassCopyMode.FIX_META_INF, remapper);
remapper.readInputs(serverJar);
try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(minecraftEnvOnlyJar).build()) {
outputConsumer.addNonClassFiles(inputJar, NonClassCopyMode.FIX_META_INF, remapper);
remapper.readInputs(inputJar);
remapper.apply(outputConsumer);
}
} catch (Exception e) {
Files.deleteIfExists(minecraftServerOnlyJar);
throw new RuntimeException("Failed to process server only jar", e);
Files.deleteIfExists(minecraftEnvOnlyJar);
throw new RuntimeException("Failed to process %s only jar".formatted(environment.name()), e);
} finally {
if (remapper != null) {
remapper.finish();
@@ -96,7 +100,54 @@ public final class ServerOnlyMinecraftProvider extends MinecraftProvider {
}
}
public Path getMinecraftServerOnlyJar() {
return minecraftServerOnlyJar;
@Override
protected boolean provideClient() {
return environment instanceof Client;
}
@Override
protected boolean provideServer() {
return environment instanceof Server;
}
public Path getMinecraftEnvOnlyJar() {
return minecraftEnvOnlyJar;
}
private interface Environment {
String name();
Path getInputJar(SingleJarMinecraftProvider provider) throws Exception;
}
private static final class Server implements Environment {
@Override
public String name() {
return "server";
}
@Override
public Path getInputJar(SingleJarMinecraftProvider provider) throws Exception {
BundleMetadata serverBundleMetadata = provider.getServerBundleMetadata();
if (serverBundleMetadata == null) {
return provider.getMinecraftServerJar().toPath();
}
provider.extractBundledServerJar();
return provider.getMinecraftExtractedServerJar().toPath();
}
}
private static final class Client implements Environment {
@Override
public String name() {
return "client";
}
@Override
public Path getInputJar(SingleJarMinecraftProvider provider) throws Exception {
return provider.getMinecraftClientJar().toPath();
}
}
}

View File

@@ -77,7 +77,8 @@ public final class SplitMinecraftProvider extends MinecraftProvider {
try (MinecraftJarSplitter jarSplitter = new MinecraftJarSplitter(clientJar, serverJar)) {
// Required for loader to compute the version info also useful to have in both jars.
jarSplitter.sharedEntry("version.json");
jarSplitter.forcedClientEntry("assets/.mcassetsroot");
jarSplitter.sharedEntry("assets/.mcassetsroot");
jarSplitter.sharedEntry("assets/minecraft/lang/en_us.json");
jarSplitter.split(minecraftClientOnlyJar, minecraftCommonJar);
} catch (Exception e) {

View File

@@ -24,18 +24,38 @@
package net.fabricmc.loom.configuration.providers.minecraft.assets;
import java.util.HashSet;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import com.fasterxml.jackson.annotation.JsonProperty;
@SuppressWarnings("unused")
public record AssetIndex(Map<String, AssetObject> objects, boolean virtual) {
public record AssetIndex(Map<String, Entry> objects, boolean virtual, @JsonProperty("map_to_resources") boolean mapToResources) {
public AssetIndex() {
this(new LinkedHashMap<>(), false);
this(new LinkedHashMap<>(), false, false);
}
public Set<AssetObject> getUniqueObjects() {
return new HashSet<>(this.objects.values());
public Collection<Object> getObjects() {
return objects.entrySet().stream().map(Object::new).toList();
}
public record Entry(String hash, long size) {
}
public record Object(String path, String hash, long size) {
private Object(Map.Entry<String, Entry> entry) {
this(entry.getKey(), entry.getValue().hash(), entry.getValue().size());
}
public String name() {
int end = path().lastIndexOf("/") + 1;
if (end > 0) {
return path().substring(end);
}
return path();
}
}
}

View File

@@ -1,169 +0,0 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2018-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.assets;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.URL;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import com.google.common.base.Stopwatch;
import me.tongfei.progressbar.DelegatingProgressBarConsumer;
import me.tongfei.progressbar.ProgressBar;
import me.tongfei.progressbar.ProgressBarBuilder;
import me.tongfei.progressbar.ProgressBarStyle;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta;
import net.fabricmc.loom.util.MirrorUtil;
import net.fabricmc.loom.util.HashedDownloadUtil;
public class MinecraftAssetsProvider {
public static void provide(MinecraftProvider minecraftProvider, Project project) throws IOException {
LoomGradleExtension extension = LoomGradleExtension.get(project);
boolean offline = project.getGradle().getStartParameter().isOffline();
MinecraftVersionMeta versionInfo = minecraftProvider.getVersionInfo();
MinecraftVersionMeta.AssetIndex assetIndex = versionInfo.assetIndex();
// get existing cache files
File assets = new File(extension.getFiles().getUserCache(), "assets");
if (!assets.exists()) {
assets.mkdirs();
}
File assetsInfo = new File(assets, "indexes" + File.separator + assetIndex.fabricId(minecraftProvider.minecraftVersion()) + ".json");
project.getLogger().info(":downloading asset index");
if (offline) {
if (assetsInfo.exists()) {
//We know it's outdated but can't do anything about it, oh well
project.getLogger().warn("Asset index outdated");
} else {
//We don't know what assets we need, just that we don't have any
throw new GradleException("Asset index not found at " + assetsInfo.getAbsolutePath());
}
} else {
HashedDownloadUtil.downloadIfInvalid(new URL(assetIndex.url()), assetsInfo, assetIndex.sha1(), project.getLogger(), false);
}
ExecutorService executor = Executors.newFixedThreadPool(Math.min(16, Math.max(Runtime.getRuntime().availableProcessors() * 2, 1)));
int toDownload = 0;
AssetIndex index;
try (FileReader fileReader = new FileReader(assetsInfo)) {
index = LoomGradlePlugin.OBJECT_MAPPER.readValue(fileReader, AssetIndex.class);
}
Stopwatch stopwatch = Stopwatch.createStarted();
Map<String, AssetObject> parent = index.objects();
ProgressBar[] progressBar = {null};
try {
for (Map.Entry<String, AssetObject> entry : parent.entrySet()) {
AssetObject object = entry.getValue();
String sha1 = object.hash();
String filename = "objects" + File.separator + sha1.substring(0, 2) + File.separator + sha1;
File file = new File(assets, filename);
if (offline) {
if (file.exists()) {
project.getLogger().warn("Outdated asset " + entry.getKey());
} else {
throw new GradleException("Asset " + entry.getKey() + " not found at " + file.getAbsolutePath());
}
} else if (HashedDownloadUtil.requiresDownload(file, sha1, project.getLogger())) {
toDownload++;
synchronized (progressBar) {
if (progressBar[0] == null) {
progressBar[0] = new ProgressBarBuilder()
.setConsumer(new DelegatingProgressBarConsumer(project.getLogger()::lifecycle))
.setInitialMax(toDownload)
.setUpdateIntervalMillis(2000)
.setTaskName(":downloading assets")
.setStyle(ProgressBarStyle.ASCII)
.showSpeed()
.build();
}
progressBar[0].maxHint(toDownload);
}
executor.execute(() -> {
String assetName = entry.getKey();
int end = assetName.lastIndexOf("/") + 1;
if (end > 0) {
assetName = assetName.substring(end);
}
project.getLogger().debug(":downloading asset " + assetName);
try {
HashedDownloadUtil.downloadIfInvalid(new URL(MirrorUtil.getResourcesBase(project) + sha1.substring(0, 2) + "/" + sha1), file, sha1, project.getLogger(), true, false);
} catch (IOException e) {
throw new RuntimeException("Failed to download: " + assetName, e);
}
synchronized (progressBar) {
progressBar[0].step();
}
});
}
}
project.getLogger().info("Took " + stopwatch.stop() + " to iterate " + parent.size() + " asset index.");
//Wait for the assets to all download
executor.shutdown();
try {
if (executor.awaitTermination(2, TimeUnit.HOURS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} finally {
if (progressBar[0] != null) {
progressBar[0].close();
}
}
}
}

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-2022 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,7 @@ package net.fabricmc.loom.configuration.providers.minecraft.mapped;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -41,6 +42,7 @@ import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets;
import net.fabricmc.loom.configuration.providers.minecraft.SignatureFixerApplyVisitor;
import net.fabricmc.loom.util.TinyRemapperHelper;
import net.fabricmc.loom.util.srg.InnerClassRemapper;
@@ -60,8 +62,8 @@ public abstract class AbstractMappedMinecraftProvider<M extends MinecraftProvide
public abstract List<RemappedJars> getRemappedJars();
protected void applyDependencies(BiConsumer<String, String> consumer) {
// Override if needed
public List<String> getDependencyTargets() {
return Collections.emptyList();
}
public void provide(boolean applyDependencies) throws Exception {
@@ -79,7 +81,16 @@ public abstract class AbstractMappedMinecraftProvider<M extends MinecraftProvide
}
if (applyDependencies) {
applyDependencies((configuration, name) -> getProject().getDependencies().add(configuration, getDependencyNotation(name)));
final List<String> dependencyTargets = getDependencyTargets();
if (dependencyTargets.isEmpty()) {
return;
}
MinecraftSourceSets.get(getProject()).applyDependencies(
(configuration, name) -> getProject().getDependencies().add(configuration, getDependencyNotation(name)),
dependencyTargets
);
}
}

View File

@@ -33,11 +33,11 @@ import org.gradle.api.Project;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.providers.minecraft.MergedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.ServerOnlyMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.SingleJarMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.SplitMinecraftProvider;
import net.fabricmc.loom.util.SidedClassVisitor;
public abstract sealed class IntermediaryMinecraftProvider<M extends MinecraftProvider> extends AbstractMappedMinecraftProvider<M> permits IntermediaryMinecraftProvider.MergedImpl, IntermediaryMinecraftProvider.ServerOnlyImpl, IntermediaryMinecraftProvider.SplitImpl {
public abstract sealed class IntermediaryMinecraftProvider<M extends MinecraftProvider> extends AbstractMappedMinecraftProvider<M> permits IntermediaryMinecraftProvider.MergedImpl, IntermediaryMinecraftProvider.SingleJarImpl, IntermediaryMinecraftProvider.SplitImpl {
public IntermediaryMinecraftProvider(Project project, M minecraftProvider) {
super(project, minecraftProvider);
}
@@ -86,16 +86,32 @@ public abstract sealed class IntermediaryMinecraftProvider<M extends MinecraftPr
}
}
public static final class ServerOnlyImpl extends IntermediaryMinecraftProvider<ServerOnlyMinecraftProvider> implements ServerOnly {
public ServerOnlyImpl(Project project, ServerOnlyMinecraftProvider minecraftProvider) {
public static final class SingleJarImpl extends IntermediaryMinecraftProvider<SingleJarMinecraftProvider> implements SingleJar {
private final String env;
private SingleJarImpl(Project project, SingleJarMinecraftProvider minecraftProvider, String env) {
super(project, minecraftProvider);
this.env = env;
}
public static SingleJarImpl server(Project project, SingleJarMinecraftProvider minecraftProvider) {
return new SingleJarImpl(project, minecraftProvider, "server");
}
public static SingleJarImpl client(Project project, SingleJarMinecraftProvider minecraftProvider) {
return new SingleJarImpl(project, minecraftProvider, "client");
}
@Override
public List<RemappedJars> getRemappedJars() {
return List.of(
new RemappedJars(minecraftProvider.getMinecraftServerOnlyJar(), getServerOnlyJar(), MappingsNamespace.OFFICIAL)
new RemappedJars(minecraftProvider.getMinecraftEnvOnlyJar(), getEnvOnlyJar(), MappingsNamespace.OFFICIAL)
);
}
@Override
public String env() {
return env;
}
}
}

View File

@@ -65,16 +65,20 @@ public interface MappedMinecraftProvider {
}
}
interface ServerOnly extends ProviderImpl {
String SERVER_ONLY = "serverOnly";
interface SingleJar extends ProviderImpl {
String env();
default Path getServerOnlyJar() {
return getJar(SERVER_ONLY);
default String envName() {
return "%sOnly".formatted(env());
}
default Path getEnvOnlyJar() {
return getJar(envName());
}
@Override
default List<Path> getMinecraftJars() {
return List.of(getServerOnlyJar());
return List.of(getEnvOnlyJar());
}
}
}

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-2022 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
@@ -26,7 +26,6 @@ package net.fabricmc.loom.configuration.providers.minecraft.mapped;
import java.nio.file.Path;
import java.util.List;
import java.util.function.BiConsumer;
import dev.architectury.tinyremapper.TinyRemapper;
import org.gradle.api.Project;
@@ -34,9 +33,8 @@ import org.gradle.api.Project;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.providers.minecraft.MergedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.ServerOnlyMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.SingleJarMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.SplitMinecraftProvider;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.SidedClassVisitor;
public abstract class NamedMinecraftProvider<M extends MinecraftProvider> extends AbstractMappedMinecraftProvider<M> {
@@ -67,8 +65,8 @@ public abstract class NamedMinecraftProvider<M extends MinecraftProvider> extend
}
@Override
protected void applyDependencies(BiConsumer<String, String> consumer) {
consumer.accept(Constants.Configurations.MINECRAFT_NAMED, MERGED);
public List<String> getDependencyTargets() {
return List.of(MERGED);
}
}
@@ -93,27 +91,42 @@ public abstract class NamedMinecraftProvider<M extends MinecraftProvider> extend
}
@Override
protected void applyDependencies(BiConsumer<String, String> consumer) {
consumer.accept(Constants.Configurations.MINECRAFT_NAMED, COMMON);
consumer.accept(Constants.Configurations.MINECRAFT_NAMED, CLIENT_ONLY);
public List<String> getDependencyTargets() {
return List.of(CLIENT_ONLY, COMMON);
}
}
public static final class ServerOnlyImpl extends NamedMinecraftProvider<ServerOnlyMinecraftProvider> implements ServerOnly {
public ServerOnlyImpl(Project project, ServerOnlyMinecraftProvider minecraftProvider) {
public static final class SingleJarImpl extends NamedMinecraftProvider<SingleJarMinecraftProvider> implements SingleJar {
private final String env;
private SingleJarImpl(Project project, SingleJarMinecraftProvider minecraftProvider, String env) {
super(project, minecraftProvider);
this.env = env;
}
public static SingleJarImpl server(Project project, SingleJarMinecraftProvider minecraftProvider) {
return new SingleJarImpl(project, minecraftProvider, "server");
}
public static SingleJarImpl client(Project project, SingleJarMinecraftProvider minecraftProvider) {
return new SingleJarImpl(project, minecraftProvider, "client");
}
@Override
public List<RemappedJars> getRemappedJars() {
return List.of(
new RemappedJars(minecraftProvider.getMinecraftServerOnlyJar(), getServerOnlyJar(), MappingsNamespace.OFFICIAL)
new RemappedJars(minecraftProvider.getMinecraftEnvOnlyJar(), getEnvOnlyJar(), MappingsNamespace.OFFICIAL)
);
}
@Override
protected void applyDependencies(BiConsumer<String, String> consumer) {
consumer.accept(Constants.Configurations.MINECRAFT_NAMED, SERVER_ONLY);
public List<String> getDependencyTargets() {
return List.of(envName());
}
@Override
public String env() {
return env;
}
}
}

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-2022 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
@@ -36,7 +36,8 @@ import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.configuration.processors.JarProcessorManager;
import net.fabricmc.loom.configuration.providers.minecraft.MergedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.ServerOnlyMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets;
import net.fabricmc.loom.configuration.providers.minecraft.SingleJarMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.SplitMinecraftProvider;
public abstract class ProcessedNamedMinecraftProvider<M extends MinecraftProvider, P extends NamedMinecraftProvider<M>> extends NamedMinecraftProvider<M> {
@@ -85,7 +86,16 @@ public abstract class ProcessedNamedMinecraftProvider<M extends MinecraftProvide
}
if (applyDependencies) {
parentMinecraftProvider.applyDependencies((configuration, name) -> getProject().getDependencies().add(configuration, getDependencyNotation(name)));
final List<String> dependencyTargets = parentMinecraftProvider.getDependencyTargets();
if (dependencyTargets.isEmpty()) {
return;
}
MinecraftSourceSets.get(getProject()).applyDependencies(
(configuration, name) -> getProject().getDependencies().add(configuration, getDependencyNotation(name)),
dependencyTargets
);
}
}
@@ -156,14 +166,30 @@ public abstract class ProcessedNamedMinecraftProvider<M extends MinecraftProvide
}
}
public static final class ServerOnlyImpl extends ProcessedNamedMinecraftProvider<ServerOnlyMinecraftProvider, NamedMinecraftProvider.ServerOnlyImpl> implements ServerOnly {
public ServerOnlyImpl(NamedMinecraftProvider.ServerOnlyImpl parentMinecraftProvide, JarProcessorManager jarProcessorManager) {
public static final class SingleJarImpl extends ProcessedNamedMinecraftProvider<SingleJarMinecraftProvider, NamedMinecraftProvider.SingleJarImpl> implements SingleJar {
private final String env;
private SingleJarImpl(NamedMinecraftProvider.SingleJarImpl parentMinecraftProvide, JarProcessorManager jarProcessorManager, String env) {
super(parentMinecraftProvide, jarProcessorManager);
this.env = env;
}
public static ProcessedNamedMinecraftProvider.SingleJarImpl server(NamedMinecraftProvider.SingleJarImpl parentMinecraftProvide, JarProcessorManager jarProcessorManager) {
return new ProcessedNamedMinecraftProvider.SingleJarImpl(parentMinecraftProvide, jarProcessorManager, "server");
}
public static ProcessedNamedMinecraftProvider.SingleJarImpl client(NamedMinecraftProvider.SingleJarImpl parentMinecraftProvide, JarProcessorManager jarProcessorManager) {
return new ProcessedNamedMinecraftProvider.SingleJarImpl(parentMinecraftProvide, jarProcessorManager, "client");
}
@Override
public Path getServerOnlyJar() {
return getProcessedPath(getParentMinecraftProvider().getServerOnlyJar());
public Path getEnvOnlyJar() {
return getProcessedPath(getParentMinecraftProvider().getEnvOnlyJar());
}
@Override
public String env() {
return env;
}
}
}

View File

@@ -40,6 +40,7 @@ public class FernflowerLogger extends IFernflowerLogger {
@Override
public void writeMessage(String message, Severity severity) {
if (message.contains("Inconsistent inner class entries for")) return;
if (message.contains("Inconsistent generic signature in method")) return;
System.err.println(message);
}

View File

@@ -49,7 +49,9 @@ import net.fabricmc.loom.api.ForgeExtensionAPI;
import net.fabricmc.loom.api.InterfaceInjectionExtensionAPI;
import net.fabricmc.loom.api.LoomGradleExtensionAPI;
import net.fabricmc.loom.api.MixinExtensionAPI;
import net.fabricmc.loom.api.ModSettings;
import net.fabricmc.loom.api.decompilers.DecompilerOptions;
import net.fabricmc.loom.api.mappings.intermediate.IntermediateMappingsProvider;
import net.fabricmc.loom.api.mappings.layered.spec.LayeredMappingSpecBuilder;
import net.fabricmc.loom.configuration.ide.RunConfig;
import net.fabricmc.loom.configuration.ide.RunConfigSettings;
@@ -61,6 +63,7 @@ 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.minecraft.MinecraftJarConfiguration;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets;
import net.fabricmc.loom.util.DeprecationHelper;
import net.fabricmc.loom.util.ModPlatform;
@@ -80,15 +83,22 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
protected final Property<String> customManifest;
protected final Property<Boolean> setupRemappedVariants;
protected final Property<Boolean> transitiveAccessWideners;
protected final Property<Boolean> modProvidedJavadoc;
protected final Property<String> intermediary;
protected final Property<IntermediateMappingsProvider> intermediateMappingsProvider;
private final Property<Boolean> runtimeOnlyLog4j;
private final Property<MinecraftJarConfiguration> minecraftJarConfiguration;
private final Property<Boolean> splitEnvironmentalSourceSet;
private final InterfaceInjectionExtensionAPI interfaceInjectionExtension;
private final ModVersionParser versionParser;
private final NamedDomainObjectContainer<RunConfigSettings> runConfigs;
private final NamedDomainObjectContainer<DecompilerOptions> decompilers;
private final NamedDomainObjectContainer<ModSettings> mods;
// 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);
// ===================
// Architectury Loom
@@ -115,9 +125,15 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
this.transitiveAccessWideners = project.getObjects().property(Boolean.class)
.convention(true);
this.transitiveAccessWideners.finalizeValueOnRead();
this.modProvidedJavadoc = project.getObjects().property(Boolean.class)
.convention(true);
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");
this.intermediateMappingsProvider = project.getObjects().property(IntermediateMappingsProvider.class);
this.intermediateMappingsProvider.finalizeValueOnRead();
this.versionParser = new ModVersionParser(project);
this.deprecationHelper = new DeprecationHelper.ProjectBased(project);
@@ -125,6 +141,7 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
this.runConfigs = project.container(RunConfigSettings.class,
baseName -> new RunConfigSettings(project, baseName));
this.decompilers = project.getObjects().domainObjectContainer(DecompilerOptions.class);
this.mods = project.getObjects().domainObjectContainer(ModSettings.class);
this.minecraftJarConfiguration = project.getObjects().property(MinecraftJarConfiguration.class).convention(MinecraftJarConfiguration.MERGED);
this.minecraftJarConfiguration.finalizeValueOnRead();
@@ -137,6 +154,9 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
this.interfaceInjectionExtension = project.getObjects().newInstance(InterfaceInjectionExtensionAPI.class);
this.splitEnvironmentalSourceSet = project.getObjects().property(Boolean.class).convention(false);
this.splitEnvironmentalSourceSet.finalizeValueOnRead();
// Add main source set by default
interfaceInjection(interfaceInjection -> {
final JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class);
@@ -202,10 +222,23 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
return jarProcessors;
}
@Override
public Dependency officialMojangMappings() {
if (layeredSpecBuilderScope.get()) {
throw new IllegalStateException("Use `officialMojangMappings()` when configuring layered mappings, not the extension method `loom.officialMojangMappings()`");
}
return layered(LayeredMappingSpecBuilder::officialMojangMappings);
}
@Override
public Dependency layered(Action<LayeredMappingSpecBuilder> action) {
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());
}
@@ -257,6 +290,11 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
return transitiveAccessWideners;
}
@Override
public Property<Boolean> getEnableModProvidedJavadoc() {
return modProvidedJavadoc;
}
protected abstract Project getProject();
protected abstract LoomFiles getFiles();
@@ -266,6 +304,26 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
return intermediary;
}
@Override
public IntermediateMappingsProvider getIntermediateMappingsProvider() {
return intermediateMappingsProvider.get();
}
@Override
public void setIntermediateMappingsProvider(IntermediateMappingsProvider intermediateMappingsProvider) {
this.intermediateMappingsProvider.set(intermediateMappingsProvider);
}
@Override
public <T extends IntermediateMappingsProvider> void setIntermediateMappingsProvider(Class<T> clazz, Action<T> action) {
T provider = getProject().getObjects().newInstance(clazz);
configureIntermediateMappingsProviderInternal(provider);
action.execute(provider);
intermediateMappingsProvider.set(provider);
}
protected abstract <T extends IntermediateMappingsProvider> void configureIntermediateMappingsProviderInternal(T provider);
@Override
public void disableDeprecatedPomGeneration(MavenPublication publication) {
net.fabricmc.loom.configuration.MavenPublication.excludePublication(publication);
@@ -281,11 +339,39 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
return runtimeOnlyLog4j;
}
@Override
public void splitEnvironmentSourceSets() {
splitMinecraftJar();
splitEnvironmentalSourceSet.set(true);
// We need to lock these values, as we setup the new source sets right away.
splitEnvironmentalSourceSet.finalizeValue();
minecraftJarConfiguration.finalizeValue();
MinecraftSourceSets.get(getProject()).evaluateSplit(getProject());
}
@Override
public boolean areEnvironmentSourceSetsSplit() {
return splitEnvironmentalSourceSet.get();
}
@Override
public InterfaceInjectionExtensionAPI getInterfaceInjection() {
return interfaceInjectionExtension;
}
@Override
public void mods(Action<NamedDomainObjectContainer<ModSettings>> action) {
action.execute(getMods());
}
@Override
public NamedDomainObjectContainer<ModSettings> getMods() {
return mods;
}
@Override
public void silentMojangMappingsLicense() {
this.silentMojangMappingsLicense = true;
@@ -362,6 +448,11 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
throw new RuntimeException("Yeah... something is really wrong");
}
@Override
protected <T extends IntermediateMappingsProvider> void configureIntermediateMappingsProviderInternal(T provider) {
throw new RuntimeException("Yeah... something is really wrong");
}
@Override
public MixinExtension getMixin() {
throw new RuntimeException("Yeah... something is really wrong");

View File

@@ -44,12 +44,14 @@ import org.gradle.api.file.FileCollection;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.ForgeExtensionAPI;
import net.fabricmc.loom.api.mappings.intermediate.IntermediateMappingsProvider;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.InstallerData;
import net.fabricmc.loom.configuration.LoomDependencyManager;
import net.fabricmc.loom.configuration.accesswidener.AccessWidenerFile;
import net.fabricmc.loom.configuration.processors.JarProcessorManager;
import net.fabricmc.loom.configuration.providers.forge.DependencyProviders;
import net.fabricmc.loom.configuration.providers.mappings.IntermediaryMappingsProvider;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.IntermediaryMinecraftProvider;
@@ -95,6 +97,13 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
this.unmappedMods = project.files();
this.forgeExtension = Suppliers.memoize(() -> isForge() ? project.getObjects().newInstance(ForgeExtensionImpl.class, project, this) : null);
this.supportsInclude = new LazyBool(() -> Boolean.parseBoolean(Objects.toString(project.findProperty(INCLUDE_PROPERTY))));
// Setup the default intermediate mappings provider.
setIntermediateMappingsProvider(IntermediaryMappingsProvider.class, provider -> {
provider.getIntermediaryUrl()
.convention(getIntermediaryUrl())
.finalizeValueOnRead();
});
}
@Override
@@ -256,6 +265,12 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
transitiveAccessWideners.addAll(accessWidenerFiles);
}
@Override
protected <T extends IntermediateMappingsProvider> void configureIntermediateMappingsProviderInternal(T provider) {
provider.getMinecraftVersion().set(getProject().provider(() -> getMinecraftProvider().minecraftVersion()));
provider.getMinecraftVersion().disallowChanges();
}
@Override
protected String getMinecraftVersion() {
return getMinecraftProvider().minecraftVersion();

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-2022 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
@@ -36,16 +36,22 @@ import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.util.PatternSet;
import net.fabricmc.loom.api.MixinExtensionAPI;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
public abstract class MixinExtensionApiImpl implements MixinExtensionAPI {
protected final Project project;
protected final Property<Boolean> useMixinAp;
private final Property<String> refmapTargetNamespace;
public MixinExtensionApiImpl(Project project) {
this.project = Objects.requireNonNull(project);
this.useMixinAp = project.getObjects().property(Boolean.class)
// .convention(project.provider(() -> LoomGradleExtension.get(project).isForge()));
.convention(true);
this.refmapTargetNamespace = project.getObjects().property(String.class)
.convention(MappingsNamespace.INTERMEDIARY.toString());
this.refmapTargetNamespace.finalizeValueOnRead();
}
protected final PatternSet add0(SourceSet sourceSet, String refmapName) {
@@ -59,6 +65,13 @@ public abstract class MixinExtensionApiImpl implements MixinExtensionAPI {
return useMixinAp;
}
@Override
public Property<String> getRefmapTargetNamespace() {
if (!getUseLegacyMixinAp().get()) throw new IllegalStateException("You need to set useLegacyMixinAp = true to configure Mixin annotation processor.");
return refmapTargetNamespace;
}
@Override
public void add(SourceSet sourceSet, String refmapName, Action<PatternSet> action) {
PatternSet pattern = add0(sourceSet, refmapName);

View File

@@ -52,7 +52,7 @@ public abstract class AbstractRunTask extends JavaExec {
@Override
public void exec() {
setWorkingDir(new File(getProject().getRootDir(), config.runDir));
setWorkingDir(new File(getProject().getProjectDir(), config.runDir));
environment(config.envVariables);
super.exec();

View File

@@ -24,20 +24,179 @@
package net.fabricmc.loom.task;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URL;
import java.util.Deque;
import java.util.Objects;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import javax.inject.Inject;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.TaskAction;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.providers.minecraft.assets.MinecraftAssetsProvider;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.configuration.ide.RunConfigSettings;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta;
import net.fabricmc.loom.configuration.providers.minecraft.assets.AssetIndex;
import net.fabricmc.loom.util.HashedDownloadUtil;
import net.fabricmc.loom.util.MirrorUtil;
import net.fabricmc.loom.util.gradle.ProgressLoggerHelper;
public abstract class DownloadAssetsTask extends AbstractLoomTask {
@Input
public abstract Property<String> getAssetsHash();
@OutputDirectory
public abstract RegularFileProperty getAssetsDirectory();
@OutputDirectory
public abstract RegularFileProperty getLegacyResourcesDirectory();
@Inject
public DownloadAssetsTask() {
final MinecraftVersionMeta versionInfo = getExtension().getMinecraftProvider().getVersionInfo();
final File assetsDir = new File(getExtension().getFiles().getUserCache(), "assets");
getAssetsDirectory().set(assetsDir);
getAssetsHash().set(versionInfo.assetIndex().sha1());
if (versionInfo.assets().equals("legacy")) {
getLegacyResourcesDirectory().set(new File(assetsDir, "/legacy/" + versionInfo.id()));
} else {
// pre-1.6 resources
RunConfigSettings client = Objects.requireNonNull(getExtension().getRunConfigs().findByName("client"), "Could not find client run config");
getLegacyResourcesDirectory().set(new File(getProject().getProjectDir(), client.getRunDir() + "/resources"));
}
getAssetsHash().finalizeValueOnRead();
getAssetsDirectory().finalizeValueOnRead();
getLegacyResourcesDirectory().finalizeValueOnRead();
}
public class DownloadAssetsTask extends AbstractLoomTask {
@TaskAction
public void downloadAssets() throws IOException {
Project project = this.getProject();
LoomGradleExtension extension = getExtension();
final Project project = this.getProject();
final File assetsDirectory = getAssetsDirectory().get().getAsFile();
final Deque<ProgressLoggerHelper> loggers = new ConcurrentLinkedDeque<>();
final ExecutorService executor = Executors.newFixedThreadPool(Math.min(10, Math.max(Runtime.getRuntime().availableProcessors() / 2, 1)));
final AssetIndex assetIndex = getAssetIndex();
MinecraftAssetsProvider.provide(extension.getMinecraftProvider(), project);
if (!assetsDirectory.exists()) {
assetsDirectory.mkdirs();
}
if (assetIndex.mapToResources()) {
getLegacyResourcesDirectory().get().getAsFile().mkdirs();
}
for (AssetIndex.Object object : assetIndex.getObjects()) {
final String path = object.path();
final String sha1 = object.hash();
final File file = getAssetsFile(object, assetIndex);
if (getProject().getGradle().getStartParameter().isOffline()) {
if (!file.exists()) {
throw new GradleException("Asset " + path + " not found at " + file.getAbsolutePath());
}
continue;
}
final Supplier<ProgressLoggerHelper> getOrCreateLogger = () -> {
ProgressLoggerHelper logger = loggers.pollFirst();
if (logger == null) {
// No logger available, create a new one
logger = ProgressLoggerHelper.getProgressFactory(project, DownloadAssetsTask.class.getName());
logger.start("Downloading assets...", "assets");
}
return logger;
};
executor.execute(() -> {
final ProgressLoggerHelper logger = getOrCreateLogger.get();
try {
HashedDownloadUtil.downloadIfInvalid(new URL(MirrorUtil.getResourcesBase(project) + sha1.substring(0, 2) + "/" + sha1), file, sha1, project.getLogger(), true, () -> {
project.getLogger().debug("downloading asset " + object.name());
logger.progress(String.format("%-30.30s", object.name()) + " - " + sha1);
});
} catch (IOException e) {
throw new UncheckedIOException("Failed to download: " + object.name(), e);
}
// Give this logger back
loggers.add(logger);
});
}
// Wait for the assets to all download
try {
executor.shutdown();
if (executor.awaitTermination(2, TimeUnit.HOURS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
loggers.forEach(ProgressLoggerHelper::completed);
}
}
private MinecraftVersionMeta.AssetIndex getAssetIndexMeta() {
MinecraftVersionMeta versionInfo = getExtension().getMinecraftProvider().getVersionInfo();
return versionInfo.assetIndex();
}
private AssetIndex getAssetIndex() throws IOException {
final LoomGradleExtension extension = getExtension();
final MinecraftProvider minecraftProvider = extension.getMinecraftProvider();
MinecraftVersionMeta.AssetIndex assetIndex = getAssetIndexMeta();
File assetsInfo = new File(getAssetsDirectory().get().getAsFile(), "indexes" + File.separator + assetIndex.fabricId(minecraftProvider.minecraftVersion()) + ".json");
getProject().getLogger().info(":downloading asset index");
if (getProject().getGradle().getStartParameter().isOffline()) {
if (assetsInfo.exists()) {
// We know it's outdated but can't do anything about it, oh well
getProject().getLogger().warn("Asset index outdated");
} else {
// We don't know what assets we need, just that we don't have any
throw new GradleException("Asset index not found at " + assetsInfo.getAbsolutePath());
}
} else {
HashedDownloadUtil.downloadIfInvalid(new URL(assetIndex.url()), assetsInfo, assetIndex.sha1(), getProject().getLogger(), false);
}
try (FileReader fileReader = new FileReader(assetsInfo)) {
return LoomGradlePlugin.OBJECT_MAPPER.readValue(fileReader, AssetIndex.class);
}
}
private File getAssetsFile(AssetIndex.Object object, AssetIndex index) {
if (index.mapToResources() || index.virtual()) {
return new File(getLegacyResourcesDirectory().get().getAsFile(), object.path());
}
final String filename = "objects" + File.separator + object.hash().substring(0, 2) + File.separator + object.hash();
return new File(getAssetsDirectory().get().getAsFile(), filename);
}
}

View File

@@ -65,6 +65,7 @@ import net.fabricmc.loom.api.decompilers.LoomDecompiler;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.accesswidener.TransitiveAccessWidenerMappingsProcessor;
import net.fabricmc.loom.configuration.ifaceinject.InterfaceInjectionProcessor;
import net.fabricmc.loom.configuration.mods.ModJavadocProcessor;
import net.fabricmc.loom.decompilers.LineNumberRemapper;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.FileSystemUtil;
@@ -342,6 +343,12 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
mappingsProcessors.add(new InterfaceInjectionProcessor(getProject()));
}
final ModJavadocProcessor javadocProcessor = ModJavadocProcessor.create(getProject());
if (javadocProcessor != null) {
mappingsProcessors.add(javadocProcessor);
}
if (mappingsProcessors.isEmpty()) {
return inputMappings;
}

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) 2016-2022 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
@@ -34,6 +34,7 @@ import org.gradle.api.tasks.TaskProvider;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.ide.RunConfigSettings;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJarConfiguration;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets;
import net.fabricmc.loom.task.launch.GenerateDLIConfigTask;
import net.fabricmc.loom.task.launch.GenerateLog4jConfigTask;
import net.fabricmc.loom.task.launch.GenerateRemapClasspathTask;
@@ -142,11 +143,30 @@ public final class LoomTasks {
extension.getRunConfigs().create("client", RunConfigSettings::client);
extension.getRunConfigs().create("server", RunConfigSettings::server);
// Remove the client run config when server only. Done by name to not remove any possible custom run configs
// Remove the client or server run config when not required. Done by name to not remove any possible custom run configs
project.afterEvaluate(p -> {
if (extension.getMinecraftJarConfiguration().get() == MinecraftJarConfiguration.SERVER_ONLY) {
extension.getRunConfigs().removeIf(settings -> settings.getName().equals("client"));
String taskName = switch (extension.getMinecraftJarConfiguration().get()) {
case SERVER_ONLY -> "client";
case CLIENT_ONLY -> "server";
default -> null;
};
if (taskName == null) {
return;
}
extension.getRunConfigs().removeIf(settings -> settings.getName().equals(taskName));
});
// Configure the run config source sets.
project.afterEvaluate(p -> {
if (!extension.areEnvironmentSourceSetsSplit()) {
return;
}
extension.getRunConfigs().configureEach(settings ->
settings.source(MinecraftSourceSets.get(project).getSourceSetForEnv(settings.getEnvironment()))
);
});
}

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-2022 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
@@ -39,17 +39,20 @@ import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import javax.inject.Inject;
@@ -69,6 +72,7 @@ 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.MapProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.SetProperty;
import org.gradle.api.tasks.Input;
@@ -89,6 +93,7 @@ import net.fabricmc.loom.build.MixinRefmapHelper;
import net.fabricmc.loom.build.nesting.IncludedJarFactory;
import net.fabricmc.loom.build.nesting.JarNester;
import net.fabricmc.loom.configuration.accesswidener.AccessWidenerFile;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets;
import net.fabricmc.loom.extension.MixinExtension;
import net.fabricmc.loom.task.service.JarManifestService;
import net.fabricmc.loom.task.service.MappingsService;
@@ -97,13 +102,16 @@ import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.FileSystemUtil;
import net.fabricmc.loom.util.LfWriter;
import net.fabricmc.loom.util.ModPlatform;
import net.fabricmc.loom.util.Pair;
import net.fabricmc.loom.util.SidedClassVisitor;
import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.loom.util.aw2at.Aw2At;
import net.fabricmc.loom.util.service.UnsafeWorkQueueHelper;
import net.fabricmc.lorenztiny.TinyMappingsReader;
public abstract class RemapJarTask extends AbstractRemapJarTask {
private static final String MANIFEST_PATH = "META-INF/MANIFEST.MF";
public static final String MANIFEST_PATH = "META-INF/MANIFEST.MF";
public static final String MANIFEST_NAMESPACE_KEY = "Fabric-Mapping-Namespace";
@InputFiles
public abstract ConfigurableFileCollection getNestedJars();
@@ -212,6 +220,16 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
if (!getAtAccessWideners().get().isEmpty()) {
params.getMappingBuildServiceUuid().set(UnsafeWorkQueueHelper.create(getProject(), MappingsService.createDefault(getProject(), getSourceNamespace().get(), getTargetNamespace().get())));
}
if (extension.areEnvironmentSourceSetsSplit()) {
final List<String> clientOnlyJarEntries = getClientOnlyJarEntries();
params.getManifestAttributes().set(Map.of(
"Fabric-Loom-Split-Environment", "true",
"Fabric-Loom-Client-Only-Entries", String.join(";", clientOnlyJarEntries)
));
params.getClientOnlyClasses().set(clientOnlyJarEntries.stream().filter(s -> s.endsWith(".class")).toList());
}
});
}
@@ -272,34 +290,14 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
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 List<String> rootPaths = getRootPaths(sourceSet.getResources().getSrcDirs());
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;
})
.map(relativePath(rootPaths))
.filter(allMixinConfigs::contains)
.toList();
@@ -346,6 +344,9 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
Property<JarManifestService> getJarManifestService();
Property<String> getTinyRemapperBuildServiceUuid();
Property<String> getMappingBuildServiceUuid();
MapProperty<String, String> getManifestAttributes();
ListProperty<String> getClientOnlyClasses();
}
public abstract static class RemapAction extends AbstractRemapAction<RemapParams> {
@@ -367,6 +368,10 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
remap();
if (getParameters().getClientOnlyClasses().isPresent()) {
markClientOnlyClasses();
}
if (!injectAccessWidener()) {
remapAccessWidener();
}
@@ -400,6 +405,15 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
}
}
private void markClientOnlyClasses() throws IOException {
final Stream<Pair<String, ZipUtils.UnsafeUnaryOperator<byte[]>>> tranformers = getParameters().getClientOnlyClasses().get().stream()
.map(s -> new Pair<>(s,
(ZipUtils.AsmClassOperator) classVisitor -> SidedClassVisitor.CLIENT.insertApplyVisitor(null, classVisitor)
));
ZipUtils.transform(outputFile, tranformers);
}
private boolean injectAccessWidener() throws IOException {
if (!getParameters().getInjectAccessWidener().isPresent()) return false;
@@ -518,8 +532,8 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
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());
getParameters().getJarManifestService().get().apply(manifest, getParameters().getManifestAttributes().get());
manifest.getMainAttributes().putValue(MANIFEST_NAMESPACE_KEY, getParameters().getTargetNamespace().get());
ByteArrayOutputStream out = new ByteArrayOutputStream();
manifest.write(out);
@@ -546,6 +560,50 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
}
}
private List<String> getClientOnlyJarEntries() {
final SourceSet clientSourceSet = MinecraftSourceSets.Split.getClientSourceSet(getProject());
final ConfigurableFileCollection output = getProject().getObjects().fileCollection();
output.from(clientSourceSet.getOutput().getClassesDirs());
output.from(clientSourceSet.getOutput().getResourcesDir());
final List<String> rootPaths = new ArrayList<>();
rootPaths.addAll(getRootPaths(clientSourceSet.getOutput().getClassesDirs().getFiles()));
rootPaths.addAll(getRootPaths(Set.of(Objects.requireNonNull(clientSourceSet.getOutput().getResourcesDir()))));
return output.getAsFileTree().getFiles().stream()
.map(relativePath(rootPaths))
.toList();
}
private static List<String> getRootPaths(Set<File> files) {
return files.stream()
.map(root -> {
String rootPath = root.getAbsolutePath().replace("\\", "/");
if (rootPath.charAt(rootPath.length() - 1) != '/') {
rootPath += '/';
}
return rootPath;
}).toList();
}
private static Function<File, String> relativePath(List<String> rootPaths) {
return file -> {
String s = file.getAbsolutePath().replace("\\", "/");
for (String rootPath : rootPaths) {
if (s.startsWith(rootPath)) {
s = s.substring(rootPath.length());
}
}
return s;
};
}
@Internal
public TinyRemapperService getTinyRemapperService() {
return tinyRemapperService.get();

View File

@@ -41,14 +41,24 @@ import org.gradle.api.logging.configuration.ConsoleOutput;
import org.gradle.api.tasks.TaskAction;
import net.fabricmc.loom.configuration.launch.LaunchProviderSettings;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.MappedMinecraftProvider;
import net.fabricmc.loom.task.AbstractLoomTask;
import net.fabricmc.loom.util.PropertyUtil;
import net.fabricmc.loom.util.gradle.SourceSetHelper;
public abstract class GenerateDLIConfigTask extends AbstractLoomTask {
@TaskAction
public void run() throws IOException {
final String nativesPath = getExtension().getFiles().getNativesDirectory(getProject()).getAbsolutePath();
final MinecraftVersionMeta versionInfo = getExtension().getMinecraftProvider().getVersionInfo();
File assetsDirectory = new File(getExtension().getFiles().getUserCache(), "assets");
if (versionInfo.assets().equals("legacy")) {
assetsDirectory = new File(assetsDirectory, "/legacy/" + versionInfo.id());
}
final LaunchConfig launchConfig = new LaunchConfig()
.property(!getExtension().isQuilt() ? "fabric.development" : "loader.development", "true")
.property(!getExtension().isQuilt() ? "fabric.remapClasspathFile" : "loader.remapClasspathFile", getExtension().getFiles().getRemapClasspathFile().getAbsolutePath())
@@ -58,12 +68,21 @@ public abstract class GenerateDLIConfigTask extends AbstractLoomTask {
.property("client", "java.library.path", nativesPath)
.property("client", "org.lwjgl.librarypath", nativesPath);
if (!getExtension().isForge()) {
if (!getExtension().isForge())
launchConfig
.argument("client", "--assetIndex")
.argument("client", getExtension().getMinecraftProvider().getVersionInfo().assetIndex().fabricId(getExtension().getMinecraftProvider().minecraftVersion()))
.argument("client", "--assetsDir")
.argument("client", new File(getExtension().getFiles().getUserCache(), "assets").getAbsolutePath());
.argument("client", assetsDirectory.getAbsolutePath());
if (getExtension().areEnvironmentSourceSetsSplit()) {
launchConfig.property("client", "fabric.gameJarPath.client", getGameJarPath("client"));
launchConfig.property("fabric.gameJarPath", getGameJarPath("common"));
}
if (!getExtension().getMods().isEmpty()) {
launchConfig.property("fabric.classPathGroups", getClassPathGroups());
}
}
if (getExtension().isQuilt()) {
@@ -132,6 +151,29 @@ public abstract class GenerateDLIConfigTask extends AbstractLoomTask {
.collect(Collectors.joining(","));
}
private String getGameJarPath(String env) {
MappedMinecraftProvider.Split split = (MappedMinecraftProvider.Split) getExtension().getNamedMinecraftProvider();
return switch (env) {
case "client" -> split.getClientOnlyJar().toAbsolutePath().toString();
case "common" -> split.getCommonJar().toAbsolutePath().toString();
default -> throw new UnsupportedOperationException();
};
}
/**
* See: https://github.com/FabricMC/fabric-loader/pull/585.
*/
private String getClassPathGroups() {
return getExtension().getMods().stream()
.map(modSettings ->
SourceSetHelper.getClasspath(modSettings, getProject()).stream()
.map(File::getAbsolutePath)
.collect(Collectors.joining(File.pathSeparator))
)
.collect(Collectors.joining(File.pathSeparator+File.pathSeparator));
}
public static class LaunchConfig {
private final Map<String, List<String>> values = new HashMap<>();

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-2022 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
@@ -25,6 +25,7 @@
package net.fabricmc.loom.task.service;
import java.io.Serializable;
import java.util.Map;
import java.util.Optional;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
@@ -70,7 +71,7 @@ public abstract class JarManifestService implements BuildService<JarManifestServ
});
}
public void apply(Manifest manifest) {
public void apply(Manifest manifest, Map<String, String> extraValues) {
// Don't set when running the reproducible build tests as it will break them when anything updates
if (Boolean.getBoolean("loom.test.reproducible")) {
return;
@@ -91,6 +92,10 @@ public abstract class JarManifestService implements BuildService<JarManifestServ
attributes.putValue("Fabric-Mixin-Version", p.getMixinVersion().get().version());
attributes.putValue("Fabric-Mixin-Group", p.getMixinVersion().get().group());
}
for (Map.Entry<String, String> entry : extraValues.entrySet()) {
attributes.putValue(entry.getKey(), entry.getValue());
}
}
private static Optional<String> getLoaderVersion(Project project) {

View File

@@ -39,10 +39,13 @@ import dev.architectury.tinyremapper.IMappingProvider;
import dev.architectury.tinyremapper.InputTag;
import dev.architectury.tinyremapper.TinyRemapper;
import org.gradle.api.Project;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.kotlin.remapping.KotlinMetadataTinyRemapperExtension;
import net.fabricmc.loom.task.AbstractRemapJarTask;
import net.fabricmc.loom.util.kotlin.KotlinClasspath;
import net.fabricmc.loom.util.kotlin.KotlinClasspathService;
import net.fabricmc.loom.util.kotlin.KotlinRemapperClassloader;
import net.fabricmc.loom.util.service.SharedService;
import net.fabricmc.loom.util.service.SharedServiceManager;
@@ -54,15 +57,15 @@ public class TinyRemapperService implements SharedService {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
final SharedServiceManager sharedServiceManager = SharedServiceManager.get(project);
final boolean legacyMixin = extension.getMixin().getUseLegacyMixinAp().get();
final boolean useKotlinExtension = project.getPluginManager().hasPlugin("org.jetbrains.kotlin.jvm");
final @Nullable KotlinClasspathService kotlinClasspathService = KotlinClasspathService.getOrCreateIfRequired(project);
// Generates an id that is used to share the remapper across projects. This tasks in the remap jar task name to handle custom remap jar tasks separately.
final var joiner = new StringJoiner(":");
joiner.add(extension.getMappingsProvider().getBuildServiceName("remapJarService", from, to));
joiner.add(remapJarTask.getName());
if (useKotlinExtension) {
joiner.add("kotlin");
if (kotlinClasspathService != null) {
joiner.add("kotlin-" + kotlinClasspathService.version());
}
if (remapJarTask.getRemapperIsolation().get()) {
@@ -76,10 +79,10 @@ public class TinyRemapperService implements SharedService {
mappings.add(MappingsService.createDefault(project, from, to).getMappingsProvider());
if (legacyMixin) {
mappings.add(MixinMappingsService.getService(SharedServiceManager.get(project), extension.getMappingsProvider()).getMappingProvider("named", "intermediary"));
mappings.add(MixinMappingsService.getService(SharedServiceManager.get(project), extension.getMappingsProvider()).getMappingProvider(from, to));
}
return new TinyRemapperService(mappings, !legacyMixin, useKotlinExtension);
return new TinyRemapperService(mappings, !legacyMixin, kotlinClasspathService);
});
service.readClasspath(remapJarTask.getClasspath().getFiles().stream().map(File::toPath).toList());
@@ -88,12 +91,14 @@ public class TinyRemapperService implements SharedService {
}
private TinyRemapper tinyRemapper;
@Nullable
private KotlinRemapperClassloader kotlinRemapperClassloader;
private final Map<String, InputTag> inputTagMap = new HashMap<>();
private final HashSet<Path> classpath = new HashSet<>();
// Set to true once remapping has started, once set no inputs can be read.
private boolean isRemapping = false;
public TinyRemapperService(List<IMappingProvider> mappings, boolean useMixinExtension, boolean useKotlinExtension) {
public TinyRemapperService(List<IMappingProvider> mappings, boolean useMixinExtension, @Nullable KotlinClasspath kotlinClasspath) {
TinyRemapper.Builder builder = TinyRemapper.newRemapper();
for (IMappingProvider provider : mappings) {
@@ -104,8 +109,9 @@ public class TinyRemapperService implements SharedService {
builder.extension(new dev.architectury.tinyremapper.extension.mixin.MixinExtension());
}
if (useKotlinExtension) {
builder.extension(KotlinMetadataTinyRemapperExtension.INSTANCE);
if (kotlinClasspath != null) {
kotlinRemapperClassloader = KotlinRemapperClassloader.create(kotlinClasspath);
builder.extension(kotlinRemapperClassloader.getTinyRemapperExtension());
}
tinyRemapper = builder.build();
@@ -156,5 +162,9 @@ public class TinyRemapperService implements SharedService {
tinyRemapper.finish();
tinyRemapper = null;
}
if (kotlinRemapperClassloader != null) {
kotlinRemapperClassloader.close();
}
}
}

View File

@@ -71,7 +71,6 @@ public class Constants {
public static final String MINECRAFT_DEPENDENCIES = "minecraftLibraries";
public static final String MINECRAFT_RUNTIME_DEPENDENCIES = "minecraftRuntimeOnlyLibraries";
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";
public static final String LOADER_DEPENDENCIES = "loaderLibraries";
@@ -178,6 +177,11 @@ public class Constants {
}
}
public static final class CustomModJsonKeys {
public static final String INJECTED_INTERFACE = "loom:injected_interfaces";
public static final String PROVIDED_JAVADOC = "loom:provided_javadoc";
}
public static final class Forge {
public static final String LAUNCH_TESTING = "net.minecraftforge.userdev.LaunchTesting";
public static final String ACCESS_TRANSFORMER_PATH = "META-INF/accesstransformer.cfg";

View File

@@ -35,7 +35,6 @@ import java.util.zip.GZIPInputStream;
import javax.annotation.Nullable;
import com.google.common.hash.Hashing;
import com.google.common.io.Files;
import org.apache.commons.io.FileUtils;
import org.gradle.api.logging.Logger;
@@ -120,13 +119,6 @@ public class HashedDownloadUtil {
throw e;
}
if (!Checksum.equals(to, expectedHash)) {
String actualHash = Files.asByteSource(to).hash(Hashing.sha1()).toString();
delete(to);
throw new IOException(String.format("Downloaded file from %s to %s and got unexpected hash of %s expected %s", from, to, actualHash, expectedHash));
}
saveSha1(to, expectedHash, logger);
}

View File

@@ -0,0 +1,76 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.util;
import java.util.List;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Preconditions;
import com.google.gson.Gson;
import kotlinx.metadata.jvm.KotlinClassMetadata;
import org.apache.commons.io.FileUtils;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.commons.ClassRemapper;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.analysis.Analyzer;
import org.objectweb.asm.util.ASMifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* It's quite common for other plugins to shade these libraries, thus the wrong version is used.
* This file logs out the version + location of each library as a debugging aid.
*
* <p>gradlew buildEnvironment is a useful command to run alongside this.
*/
public final class LibraryLocationLogger {
private static final List<Class<?>> libraryClasses = List.of(
KotlinClassMetadata.class,
ClassVisitor.class,
Analyzer.class,
ClassRemapper.class,
ClassNode.class,
ASMifier.class,
ObjectMapper.class,
Gson.class,
Preconditions.class,
FileUtils.class
);
private static final Logger LOGGER = LoggerFactory.getLogger(LibraryLocationLogger.class);
public static void logLibraryVersions() {
for (Class<?> clazz : libraryClasses) {
LOGGER.info("({}) with version ({}) was loaded from ({})",
clazz.getName(),
clazz.getPackage().getImplementationVersion(),
clazz.getProtectionDomain().getCodeSource().getLocation().getPath()
);
}
}
private LibraryLocationLogger() {
}
}

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) 2016-2022 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
@@ -25,21 +25,50 @@
package net.fabricmc.loom.util;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import com.google.gson.JsonObject;
import org.gradle.api.logging.Logger;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.LoomGradlePlugin;
public final class ModUtils {
private ModUtils() {
}
public static boolean isMod(File input, ModPlatform platform) {
public static boolean isMod(File file, ModPlatform platform) {
return isMod(file.toPath());
}
public static boolean isMod(Path input, ModPlatform platform) {
if (platform == ModPlatform.FORGE) {
return ZipUtils.contains(input.toPath(), "META-INF/mods.toml");
} else if (platform == ModPlatform.QUILT) {
return ZipUtils.contains(input.toPath(), "quilt.mod.json");
}
return ZipUtils.contains(input.toPath(), "fabric.mod.json");
return ZipUtils.contains(input, "fabric.mod.json");
}
@Nullable
public static JsonObject getFabricModJson(Path path) {
final byte[] modJsonBytes;
try {
modJsonBytes = ZipUtils.unpackNullable(path, "fabric.mod.json");
} catch (IOException e) {
throw new UncheckedIOException("Failed to extract fabric.mod.json from " + path, e);
}
if (modJsonBytes == null) {
return null;
}
return LoomGradlePlugin.GSON.fromJson(new String(modJsonBytes, StandardCharsets.UTF_8), JsonObject.class);
}
public static boolean shouldRemapMod(Logger logger, File input, Object id, ModPlatform platform, String config) {

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-2022 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
@@ -41,6 +41,7 @@ public final class SidedClassVisitor extends ClassVisitor {
private static final String SIDE_DESCRIPTOR = "Lnet/fabricmc/api/EnvType;";
private final String side;
private boolean hasExisting = false;
private SidedClassVisitor(String side, ClassVisitor next) {
super(Constants.ASM_VERSION, next);
@@ -48,11 +49,22 @@ public final class SidedClassVisitor extends ClassVisitor {
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
if (ENVIRONMENT_DESCRIPTOR.equals(descriptor)) {
hasExisting = true;
}
final AnnotationVisitor annotationVisitor = visitAnnotation(ENVIRONMENT_DESCRIPTOR, true);
annotationVisitor.visitEnum("value", SIDE_DESCRIPTOR, side.toUpperCase(Locale.ROOT));
annotationVisitor.visitEnd();
return super.visitAnnotation(descriptor, visible);
}
@Override
public void visitEnd() {
if (!hasExisting) {
final AnnotationVisitor annotationVisitor = visitAnnotation(ENVIRONMENT_DESCRIPTOR, true);
annotationVisitor.visitEnum("value", SIDE_DESCRIPTOR, side.toUpperCase(Locale.ROOT));
annotationVisitor.visitEnd();
}
super.visitEnd();
}
}

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-2022 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
@@ -44,6 +44,9 @@ import java.util.function.Function;
import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import net.fabricmc.loom.LoomGradlePlugin;
@@ -231,6 +234,20 @@ public class ZipUtils {
T apply(T arg) throws IOException;
}
public interface AsmClassOperator extends UnsafeUnaryOperator<byte[]> {
ClassVisitor visit(ClassVisitor classVisitor);
@Override
default byte[] apply(byte[] arg) throws IOException {
final ClassReader reader = new ClassReader(arg);
final ClassWriter writer = new ClassWriter(0);
reader.accept(visit(writer), 0);
return writer.toByteArray();
}
}
private static <T> Map<String, UnsafeUnaryOperator<T>> collectTransformersStream(Stream<Pair<String, UnsafeUnaryOperator<T>>> transforms) {
Map<String, UnsafeUnaryOperator<T>> map = new HashMap<>();
Iterator<Pair<String, UnsafeUnaryOperator<T>>> iterator = transforms.iterator();

View File

@@ -0,0 +1,172 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 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.io.FileInputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.gradle.api.Project;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.SourceSetOutput;
import org.intellij.lang.annotations.Language;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.VisibleForTesting;
import org.xml.sax.InputSource;
import net.fabricmc.loom.api.ModSettings;
import net.fabricmc.loom.configuration.ide.idea.IdeaUtils;
public final class SourceSetHelper {
@VisibleForTesting
@Language("xpath")
public static final String IDEA_OUTPUT_XPATH = "/project/component[@name='ProjectRootManager']/output/@url";
private SourceSetHelper() {
}
public static List<File> getClasspath(ModSettings modSettings, Project project) {
final List<File> files = new ArrayList<>();
files.addAll(modSettings.getModSourceSets().get().stream()
.flatMap(sourceSet -> getClasspath(sourceSet, project).stream())
.toList());
files.addAll(modSettings.getModFiles().getFiles());
return Collections.unmodifiableList(files);
}
public static List<File> getClasspath(SourceSet sourceSet, Project project) {
final List<File> classpath = getGradleClasspath(sourceSet);
classpath.addAll(getIdeaClasspath(sourceSet, project));
classpath.addAll(getEclipseClasspath(sourceSet, project));
classpath.addAll(getVscodeClasspath(sourceSet, project));
return classpath;
}
private static List<File> getGradleClasspath(SourceSet sourceSet) {
final SourceSetOutput output = sourceSet.getOutput();
final File resources = output.getResourcesDir();
final List<File> classpath = new ArrayList<>();
classpath.addAll(output.getClassesDirs().getFiles());
if (resources != null) {
classpath.add(resources);
}
return classpath;
}
@VisibleForTesting
public static List<File> getIdeaClasspath(SourceSet sourceSet, Project project) {
final File projectDir = project.getRootDir();
final File dotIdea = new File(projectDir, ".idea");
if (!dotIdea.exists()) {
return Collections.emptyList();
}
final File miscXml = new File(dotIdea, "misc.xml");
if (!miscXml.exists()) {
return Collections.emptyList();
}
String outputDirUrl = evaluateXpath(miscXml, IDEA_OUTPUT_XPATH);
if (outputDirUrl == null) {
return Collections.emptyList();
}
outputDirUrl = outputDirUrl.replace("$PROJECT_DIR$", projectDir.getAbsolutePath());
outputDirUrl = outputDirUrl.replaceAll("^file:", "");
final File productionDir = new File(outputDirUrl, "production");
final File outputDir = new File(productionDir, IdeaUtils.getIdeaModuleName(project, sourceSet));
return Collections.singletonList(outputDir);
}
@Nullable
private static String evaluateXpath(File file, @Language("xpath") String expression) {
final XPath xpath = XPathFactory.newInstance().newXPath();
try (FileInputStream fis = new FileInputStream(file)) {
return xpath.evaluate(expression, new InputSource(fis));
} catch (XPathExpressionException e) {
return null;
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
@VisibleForTesting
public static List<File> getEclipseClasspath(SourceSet sourceSet, Project project) {
// Somewhat of a guess, I'm unsure if this is correct for multi-project builds
final File projectDir = project.getProjectDir();
final File classpath = new File(projectDir, ".classpath");
if (!classpath.exists()) {
return Collections.emptyList();
}
return getBinDirClasspath(projectDir, sourceSet);
}
@VisibleForTesting
public static List<File> getVscodeClasspath(SourceSet sourceSet, Project project) {
// Somewhat of a guess, I'm unsure if this is correct for multi-project builds
final File projectDir = project.getProjectDir();
final File dotVscode = new File(projectDir, ".vscode");
if (!dotVscode.exists()) {
return Collections.emptyList();
}
return getBinDirClasspath(projectDir, sourceSet);
}
private static List<File> getBinDirClasspath(File projectDir, SourceSet sourceSet) {
final File binDir = new File(projectDir, "bin");
if (!binDir.exists()) {
return Collections.emptyList();
}
return Collections.singletonList(new File(binDir, sourceSet.getName()));
}
}

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) 2022 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,8 +22,13 @@
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.providers.minecraft.assets;
package net.fabricmc.loom.util.kotlin;
@SuppressWarnings("unused")
public record AssetObject(String hash, long size) {
import java.net.URL;
import java.util.Set;
public interface KotlinClasspath {
String version();
Set<URL> classpath();
}

View File

@@ -0,0 +1,75 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 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.kotlin;
import java.io.UncheckedIOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Set;
import java.util.stream.Collectors;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.util.service.SharedService;
import net.fabricmc.loom.util.service.SharedServiceManager;
public record KotlinClasspathService(Set<URL> classpath, String version) implements KotlinClasspath, SharedService {
@Nullable
public static KotlinClasspathService getOrCreateIfRequired(Project project) {
if (!KotlinPluginUtils.hasKotlinPlugin(project)) {
return null;
}
return getOrCreate(project, KotlinPluginUtils.getKotlinPluginVersion(project), KotlinPluginUtils.getKotlinMetadataVersion());
}
public static synchronized KotlinClasspathService getOrCreate(Project project, String kotlinVersion, String kotlinMetadataVersion) {
final String id = "kotlinclasspath:%s:%s".formatted(kotlinVersion, kotlinMetadataVersion);
final SharedServiceManager sharedServiceManager = SharedServiceManager.get(project);
return sharedServiceManager.getOrCreateService(id, () -> create(project, kotlinVersion, kotlinMetadataVersion));
}
private static KotlinClasspathService create(Project project, String kotlinVersion, String kotlinMetadataVersion) {
// Create a detached config to resolve the kotlin std lib for the provided version.
Configuration detachedConfiguration = project.getConfigurations().detachedConfiguration(
project.getDependencies().create("org.jetbrains.kotlin:kotlin-stdlib:" + kotlinVersion),
// Load kotlinx-metadata-jvm like this to work around: https://github.com/gradle/gradle/issues/14727
project.getDependencies().create("org.jetbrains.kotlinx:kotlinx-metadata-jvm:" + kotlinMetadataVersion)
);
Set<URL> classpath = detachedConfiguration.getFiles().stream()
.map(file -> {
try {
return file.toURI().toURL();
} catch (MalformedURLException e) {
throw new UncheckedIOException(e);
}
}).collect(Collectors.toSet());;
return new KotlinClasspathService(classpath, kotlinVersion);
}
}

View File

@@ -0,0 +1,30 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 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.kotlin;
import net.fabricmc.tinyremapper.TinyRemapper;
public interface KotlinMetadataTinyRemapperExtension extends TinyRemapper.ApplyVisitorProvider, TinyRemapper.Extension {
}

View File

@@ -0,0 +1,45 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 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.kotlin;
import kotlinx.metadata.jvm.KotlinClassMetadata;
import org.gradle.api.Project;
public class KotlinPluginUtils {
private static final String KOTLIN_PLUGIN_ID = "org.jetbrains.kotlin.jvm";
public static boolean hasKotlinPlugin(Project project) {
return project.getPluginManager().hasPlugin(KOTLIN_PLUGIN_ID);
}
public static String getKotlinPluginVersion(Project project) {
Class<?> koltinPluginClass = project.getPlugins().getPlugin(KOTLIN_PLUGIN_ID).getClass();
return koltinPluginClass.getPackage().getImplementationVersion().split("-")[0];
}
public static String getKotlinMetadataVersion() {
return KotlinClassMetadata.class.getPackage().getImplementationVersion();
}
}

View File

@@ -0,0 +1,90 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 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.kotlin;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.kotlin.remapping.KotlinMetadataTinyRemapperExtensionImpl;
/**
* Used to run the Kotlin remapper with a specific version of Koltin that may not match the kotlin version included with gradle.
*/
public class KotlinRemapperClassloader extends URLClassLoader {
// Packages that should be loaded from the gradle plugin classloader.
private static final List<String> PARENT_PACKAGES = List.of(
"net.fabricmc.tinyremapper",
"net.fabricmc.loom.util.kotlin",
"org.objectweb.asm",
"org.slf4j"
);
private KotlinRemapperClassloader(URL[] urls) {
super(urls, null);
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (PARENT_PACKAGES.stream().anyMatch(name::startsWith)) {
return LoomGradlePlugin.class.getClassLoader().loadClass(name);
}
return super.loadClass(name, resolve);
}
public static KotlinRemapperClassloader create(KotlinClasspath classpathProvider) {
// Include the libraries that are not on the kotlin classpath.
final Stream<URL> loomUrls = getClassUrls(
KotlinMetadataTinyRemapperExtensionImpl.class // Loom
);
final URL[] urls = Stream.concat(
loomUrls,
classpathProvider.classpath().stream()
).toArray(URL[]::new);
return new KotlinRemapperClassloader(urls);
}
private static Stream<URL> getClassUrls(Class<?>... classes) {
return Arrays.stream(classes).map(klass -> klass.getProtectionDomain().getCodeSource().getLocation());
}
/**
* Load the {@link KotlinMetadataTinyRemapperExtensionImpl} class on the new classloader.
*/
public KotlinMetadataTinyRemapperExtension getTinyRemapperExtension() {
try {
Class<?> klass = this.loadClass(KotlinMetadataTinyRemapperExtensionImpl.class.getCanonicalName());
return (KotlinMetadataTinyRemapperExtension) klass.getField("INSTANCE").get(null);
} catch (ClassNotFoundException | IllegalAccessException | NoSuchFieldException e) {
throw new RuntimeException("Failed to create instance", e);
}
}
}