Split mod dependencies into client/common as required.

This commit is contained in:
modmuss50
2022-08-04 08:56:37 +01:00
committed by GitHub
parent 3913c2e897
commit e561cca19a
37 changed files with 1045 additions and 515 deletions

View File

@@ -82,16 +82,12 @@ public interface LoomGradleExtensionAPI {
* 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
NamedDomainObjectList<RemapConfigurationSettings> getRemapConfigurations();
@ApiStatus.Experimental
RemapConfigurationSettings addRemapConfiguration(String name, Action<RemapConfigurationSettings> action);
void createRemapConfigurations(SourceSet sourceSet);
@@ -173,29 +169,25 @@ public interface LoomGradleExtensionAPI {
*/
Property<String> getIntermediaryUrl();
@ApiStatus.Experimental
Property<MinecraftJarConfiguration> getMinecraftJarConfiguration();
@ApiStatus.Experimental
default void serverOnlyMinecraftJar() {
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();
Property<Boolean> getSplitModDependencies();
}

View File

@@ -41,7 +41,6 @@ import net.fabricmc.loom.util.gradle.SourceSetReference;
/**
* 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, or jar files used to populate the `fabric.classPathGroups` Fabric Loader system property.

View File

@@ -54,7 +54,7 @@ public abstract class RemapConfigurationSettings implements Named {
this.name = name;
getTargetConfigurationName().finalizeValueOnRead();
getClientTargetConfigurationName().finalizeValueOnRead();
getClientSourceConfigurationName().finalizeValueOnRead();
getOnCompileClasspath().finalizeValueOnRead();
getOnRuntimeClasspath().finalizeValueOnRead();
getPublishingMode().convention(PublishingMode.NONE).finalizeValueOnRead();
@@ -74,9 +74,9 @@ public abstract class RemapConfigurationSettings implements Named {
* Optional, only used when split sourcesets are enabled.
* When not present client only entries should go onto the target configuration.
*
* @return The client target configuration name
* @return The client source configuration name
*/
public abstract Property<String> getClientTargetConfigurationName();
public abstract Property<String> getClientSourceConfigurationName();
/**
* @return True if this configuration's artifacts should be exposed for compile operations.
@@ -119,6 +119,12 @@ public abstract class RemapConfigurationSettings implements Named {
return getName() + "Mapped";
}
@ApiStatus.Internal
@Internal
public final Provider<String> getClientRemappedConfigurationName() {
return getClientSourceConfigurationName().map(s -> s + "Mapped");
}
@ApiStatus.Internal
@Internal
public final NamedDomainObjectProvider<Configuration> getSourceConfiguration() {
@@ -143,7 +149,17 @@ public abstract class RemapConfigurationSettings implements Named {
return getProject().provider(() -> {
boolean split = LoomGradleExtension.get(getProject()).getMinecraftJarConfiguration().get() == MinecraftJarConfiguration.SPLIT;
Preconditions.checkArgument(split, "Cannot get client target configuration when project is not split");
return getConfigurationByName(getClientTargetConfigurationName().get()).get();
return getConfigurationByName(getClientSourceConfigurationName().get()).get();
});
}
@ApiStatus.Internal
@Internal
public final Provider<Configuration> getClientRemappedConfiguration() {
return getProject().provider(() -> {
boolean split = LoomGradleExtension.get(getProject()).getMinecraftJarConfiguration().get() == MinecraftJarConfiguration.SPLIT;
Preconditions.checkArgument(split, "Cannot get client remapped configuration when project is not split");
return getConfigurationByName(getClientRemappedConfigurationName().get()).get();
});
}

View File

@@ -49,8 +49,8 @@ import net.fabricmc.loom.build.mixin.ScalaApInvoker;
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.processors.ModJavadocProcessor;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJarConfiguration;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
@@ -60,6 +60,7 @@ import net.fabricmc.loom.configuration.providers.minecraft.mapped.NamedMinecraft
import net.fabricmc.loom.extension.MixinExtension;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.ExceptionUtil;
import net.fabricmc.loom.util.gradle.GradleUtils;
import net.fabricmc.loom.util.gradle.SourceSetHelper;
public final class CompileConfiguration {
@@ -116,15 +117,15 @@ public final class CompileConfiguration {
project.getDependencies().add(JavaPlugin.TEST_COMPILE_ONLY_CONFIGURATION_NAME, Constants.Dependencies.JETBRAINS_ANNOTATIONS + Constants.Dependencies.Versions.JETBRAINS_ANNOTATIONS);
}
public static void configureCompile(Project p) {
LoomGradleExtension extension = LoomGradleExtension.get(p);
public static void configureCompile(Project project) {
LoomGradleExtension extension = LoomGradleExtension.get(project);
p.getTasks().named(JavaPlugin.JAVADOC_TASK_NAME, Javadoc.class).configure(javadoc -> {
final SourceSet main = SourceSetHelper.getMainSourceSet(p);
project.getTasks().named(JavaPlugin.JAVADOC_TASK_NAME, Javadoc.class).configure(javadoc -> {
final SourceSet main = SourceSetHelper.getMainSourceSet(project);
javadoc.setClasspath(main.getOutput().plus(main.getCompileClasspath()));
});
p.afterEvaluate(project -> {
GradleUtils.afterSuccessfulEvaluation(project, () -> {
MinecraftSourceSets.get(project).afterEvaluate(project);
final boolean previousRefreshDeps = extension.refreshDeps();
@@ -156,20 +157,20 @@ public final class CompileConfiguration {
configureDecompileTasks(project);
});
finalizedBy(p, "idea", "genIdeaWorkspace");
finalizedBy(p, "eclipse", "genEclipseRuns");
finalizedBy(p, "cleanEclipse", "cleanEclipseRuns");
finalizedBy(project, "idea", "genIdeaWorkspace");
finalizedBy(project, "eclipse", "genEclipseRuns");
finalizedBy(project, "cleanEclipse", "cleanEclipseRuns");
// Add the "dev" jar to the "namedElements" configuration
p.artifacts(artifactHandler -> artifactHandler.add(Constants.Configurations.NAMED_ELEMENTS, p.getTasks().named("jar")));
project.artifacts(artifactHandler -> artifactHandler.add(Constants.Configurations.NAMED_ELEMENTS, project.getTasks().named("jar")));
// Ensure that the encoding is set to UTF-8, no matter what the system default is
// this fixes some edge cases with special characters not displaying correctly
// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
p.getTasks().withType(AbstractCopyTask.class).configureEach(abstractCopyTask -> abstractCopyTask.setFilteringCharset(StandardCharsets.UTF_8.name()));
p.getTasks().withType(JavaCompile.class).configureEach(javaCompile -> javaCompile.getOptions().setEncoding(StandardCharsets.UTF_8.name()));
project.getTasks().withType(AbstractCopyTask.class).configureEach(abstractCopyTask -> abstractCopyTask.setFilteringCharset(StandardCharsets.UTF_8.name()));
project.getTasks().withType(JavaCompile.class).configureEach(javaCompile -> javaCompile.getOptions().setEncoding(StandardCharsets.UTF_8.name()));
if (p.getPluginManager().hasPlugin("org.jetbrains.kotlin.kapt")) {
if (project.getPluginManager().hasPlugin("org.jetbrains.kotlin.kapt")) {
// If loom is applied after kapt, then kapt will use the AP arguments too early for loom to pass the arguments we need for mixin.
throw new IllegalArgumentException("fabric-loom must be applied BEFORE kapt in the plugins { } block.");
}

View File

@@ -25,8 +25,6 @@
package net.fabricmc.loom.configuration;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
@@ -41,8 +39,8 @@ import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.LoomRepositoryPlugin;
import net.fabricmc.loom.configuration.mods.ModConfigurationRemapper;
import net.fabricmc.loom.configuration.ide.idea.IdeaUtils;
import net.fabricmc.loom.configuration.mods.ModConfigurationRemapper;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.SourceRemapper;
import net.fabricmc.loom.util.ZipUtils;
@@ -102,8 +100,8 @@ public class LoomDependencyManager {
}
return LoomGradlePlugin.GSON.fromJson(new String(bytes, StandardCharsets.UTF_8), JsonObject.class);
} catch (IOException e) {
throw new UncheckedIOException("Failed to try and read installer json from", e);
} catch (Exception e) {
throw new RuntimeException("Failed to try and read installer json from " + file, e);
}
}

View File

@@ -47,6 +47,7 @@ import org.gradle.api.publish.PublishingExtension;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.util.DeprecationHelper;
import net.fabricmc.loom.util.GroovyXmlUtil;
import net.fabricmc.loom.util.gradle.GradleUtils;
public final class MavenPublication {
// ImmutableMap is needed since it guarantees ordering
@@ -61,14 +62,14 @@ public final class MavenPublication {
}
public static void configure(Project project) {
project.afterEvaluate((p) -> {
GradleUtils.afterSuccessfulEvaluation(project, () -> {
AtomicBoolean reportedDeprecation = new AtomicBoolean(false);
CONFIGURATION_TO_SCOPE.forEach((configurationName, scope) -> {
Configuration config = p.getConfigurations().getByName(configurationName);
Configuration config = project.getConfigurations().getByName(configurationName);
// add modsCompile to maven-publish
PublishingExtension mavenPublish = p.getExtensions().findByType(PublishingExtension.class);
PublishingExtension mavenPublish = project.getExtensions().findByType(PublishingExtension.class);
if (mavenPublish != null) {
processEntry(project, scope, config, mavenPublish, reportedDeprecation);

View File

@@ -76,11 +76,34 @@ public final class RemapConfigurations {
// Apply the client target names to the main configurations
for (ConfigurationOption option : getValidOptions(mainSourceSet)) {
configurations.getByName(option.name(mainSourceSet), settings -> {
settings.getClientTargetConfigurationName().convention(option.targetName(clientSourceSet));
String name = option.targetName(clientSourceSet);
if (name == null) {
return;
}
settings.getClientSourceConfigurationName().set(name);
createClientMappedConfiguration(project, settings, clientSourceSet);
});
}
}
private static void createClientMappedConfiguration(Project project, RemapConfigurationSettings settings, SourceSet clientSourceSet) {
final Configuration remappedConfiguration = project.getConfigurations().create(settings.getClientRemappedConfigurationName().get());
// Don't get transitive deps of already remapped mods
remappedConfiguration.setTransitive(false);
if (settings.getOnCompileClasspath().get()) {
extendsFrom(Constants.Configurations.MOD_COMPILE_CLASSPATH_MAPPED, remappedConfiguration, project);
extendsFrom(clientSourceSet.getCompileClasspathConfigurationName(), remappedConfiguration, project);
}
if (settings.getOnRuntimeClasspath().get()) {
extendsFrom(clientSourceSet.getRuntimeClasspathConfigurationName(), remappedConfiguration, project);
}
}
public static void applyToProject(Project project, RemapConfigurationSettings settings) {
// No point bothering to make it lazily, gradle realises configurations right away.
// <https://github.com/gradle/gradle/blob/v7.4.2/subprojects/plugins/src/main/java/org/gradle/api/plugins/BasePlugin.java#L104>

View File

@@ -0,0 +1,77 @@
/*
* 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.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import com.google.gson.JsonObject;
import org.objectweb.asm.commons.Remapper;
import net.fabricmc.accesswidener.AccessWidenerReader;
import net.fabricmc.accesswidener.AccessWidenerRemapper;
import net.fabricmc.accesswidener.AccessWidenerWriter;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.util.ZipUtils;
public class AccessWidenerUtils {
/**
* Remap a mods access widener from intermediary to named, so that loader can apply it in our dev-env.
*/
public static byte[] remapAccessWidener(byte[] input, Remapper remapper) {
int version = AccessWidenerReader.readVersion(input);
AccessWidenerWriter writer = new AccessWidenerWriter(version);
AccessWidenerRemapper awRemapper = new AccessWidenerRemapper(
writer,
remapper,
MappingsNamespace.INTERMEDIARY.toString(),
MappingsNamespace.NAMED.toString()
);
AccessWidenerReader reader = new AccessWidenerReader(awRemapper);
reader.read(input);
return writer.write();
}
public static AccessWidenerData readAccessWidenerData(Path inputJar) throws IOException {
byte[] modJsonBytes = ZipUtils.unpack(inputJar, "fabric.mod.json");
JsonObject jsonObject = LoomGradlePlugin.GSON.fromJson(new String(modJsonBytes, StandardCharsets.UTF_8), JsonObject.class);
if (!jsonObject.has("accessWidener")) {
return null;
}
String accessWidenerPath = jsonObject.get("accessWidener").getAsString();
byte[] accessWidener = ZipUtils.unpack(inputJar, accessWidenerPath);
AccessWidenerReader.Header header = AccessWidenerReader.readHeader(accessWidener);
return new AccessWidenerData(accessWidenerPath, header, accessWidener);
}
public record AccessWidenerData(String path, AccessWidenerReader.Header header, byte[] content) {
}
}

View File

@@ -26,17 +26,23 @@ package net.fabricmc.loom.configuration.mods;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.task.AbstractRemapJarTask;
import net.fabricmc.loom.util.FileSystemUtil;
@@ -47,15 +53,83 @@ public class JarSplitter {
this.inputJar = inputJar;
}
public boolean split(Path commonOutputJar, Path clientOutputJar) throws IOException {
@Nullable
public Target analyseTarget() {
try (FileSystemUtil.Delegate input = FileSystemUtil.getJarFileSystem(inputJar)) {
final Manifest manifest = input.fromInputStream(Manifest::new, AbstractRemapJarTask.MANIFEST_PATH);
if (!Boolean.parseBoolean(manifest.getMainAttributes().getValue(AbstractRemapJarTask.MANIFEST_SPLIT_ENV_KEY))) {
// Jar was not built with splitting enabled.
return null;
}
final HashSet<String> clientEntries = new HashSet<>(readClientEntries(manifest));
if (clientEntries.isEmpty()) {
// No client entries.
return Target.COMMON_ONLY;
}
final List<String> entries = new LinkedList<>();
// Must collect all the input entries to see if this might be a client only jar.
try (Stream<Path> walk = Files.walk(input.get().getPath("/"))) {
final Iterator<Path> iterator = walk.iterator();
while (iterator.hasNext()) {
final Path entry = iterator.next();
if (!Files.isRegularFile(entry)) {
continue;
}
final Path relativePath = input.get().getPath("/").relativize(entry);
if (relativePath.startsWith("META-INF")) {
if (isSignatureData(relativePath)) {
// Ignore any signature data
continue;
}
if (relativePath.endsWith("MANIFEST.MF")) {
// Ignore the manifest
continue;
}
}
entries.add(relativePath.toString());
}
}
for (String entry : entries) {
if (!clientEntries.contains(entry)) {
// Found a common entry, we need to split,.
return Target.SPLIT;
}
}
// All input entries are client only entries.
return Target.CLIENT_ONLY;
} catch (IOException e) {
throw new UncheckedIOException("Failed to read jar", e);
}
}
public boolean split(Path commonOutputJar, Path clientOutputJar) throws IOException {
Files.deleteIfExists(commonOutputJar);
Files.deleteIfExists(clientOutputJar);
try (FileSystemUtil.Delegate input = FileSystemUtil.getJarFileSystem(inputJar)) {
final Manifest manifest = input.fromInputStream(Manifest::new, AbstractRemapJarTask.MANIFEST_PATH);
if (!Boolean.parseBoolean(manifest.getMainAttributes().getValue(AbstractRemapJarTask.MANIFEST_SPLIT_ENV_KEY))) {
throw new UnsupportedOperationException("Cannot split jar that has not been built with a split env");
}
final List<String> clientEntries = readClientEntries(manifest);
if (clientEntries.isEmpty()) {
// No client entries, just copy the input jar
Files.copy(inputJar, commonOutputJar);
return false;
throw new IllegalStateException("Expected to split jar with no client entries");
}
try (FileSystemUtil.Delegate commonOutput = FileSystemUtil.getJarFileSystem(commonOutputJar, true);
@@ -126,15 +200,10 @@ public class JarSplitter {
private List<String> readClientEntries(Manifest manifest) {
final Attributes attributes = manifest.getMainAttributes();
final String splitEnvValue = attributes.getValue(AbstractRemapJarTask.MANIFEST_SPLIT_ENV_KEY);
final String clientEntriesValue = attributes.getValue(AbstractRemapJarTask.MANIFEST_CLIENT_ENTRIES_KEY);
if (splitEnvValue == null || !splitEnvValue.equals("true")) {
throw new UnsupportedOperationException("Cannot split jar that has not been built with a split env");
}
if (clientEntriesValue == null) {
throw new IllegalStateException("Split jar does not contain any client only classes");
if (clientEntriesValue == null || clientEntriesValue.isBlank()) {
return Collections.emptyList();
}
return Arrays.stream(clientEntriesValue.split(";")).toList();
@@ -175,4 +244,25 @@ public class JarSplitter {
Files.write(path, bytes);
}
public enum Target {
COMMON_ONLY(true, false),
CLIENT_ONLY(false, true),
SPLIT(true, true);
final boolean common, client;
Target(boolean common, boolean client) {
this.common = common;
this.client = client;
}
public boolean common() {
return common;
}
public boolean client() {
return client;
}
}
}

View File

@@ -26,12 +26,13 @@ package net.fabricmc.loom.configuration.mods;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import com.google.common.io.Files;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.FileCollectionDependency;
@@ -50,8 +51,8 @@ import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.RemapConfigurationSettings;
import net.fabricmc.loom.configuration.processors.dependency.ModDependencyInfo;
import net.fabricmc.loom.configuration.processors.dependency.RemapData;
import net.fabricmc.loom.configuration.mods.dependency.ModDependency;
import net.fabricmc.loom.configuration.mods.dependency.ModDependencyFactory;
import net.fabricmc.loom.util.Checksum;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.ModUtils;
@@ -67,9 +68,6 @@ public class ModConfigurationRemapper {
public static void supplyModConfigurations(Project project, String mappingsSuffix, LoomGradleExtension extension, SourceRemapper sourceRemapper) {
final DependencyHandler dependencies = project.getDependencies();
final File modStore = extension.getFiles().getRemappedModCache();
final RemapData remapData = new RemapData(mappingsSuffix, modStore);
for (RemapConfigurationSettings entry : extension.getRemapConfigurations()) {
entry.getRemappedConfiguration().configure(remappedConfig -> {
/*
@@ -79,15 +77,15 @@ public class ModConfigurationRemapper {
*/
final Configuration sourceConfig = entry.getSourceConfiguration().get();
final Configuration targetConfig = entry.getTargetConfiguration().get();
final boolean hasClientTarget = entry.getClientTargetConfigurationName().isPresent();
final boolean hasClientTarget = entry.getClientSourceConfigurationName().isPresent();
Configuration clientRemappedConfig = null;
if (hasClientTarget) {
clientRemappedConfig = entry.getClientTargetConfiguration().get();
clientRemappedConfig = entry.getClientRemappedConfiguration().get();
}
final List<ModDependencyInfo> modDependencies = new ArrayList<>();
final List<ModDependency> modDependencies = new ArrayList<>();
for (ArtifactRef artifact : resolveArtifacts(project, sourceConfig)) {
if (!ModUtils.isMod(artifact.path())) {
@@ -95,47 +93,36 @@ public class ModConfigurationRemapper {
continue;
}
final ModDependencyInfo info = new ModDependencyInfo(artifact, remappedConfig, clientRemappedConfig, remapData);
if (extension.refreshDeps()) {
info.forceRemap();
}
if (artifact.sources() != null) {
scheduleSourcesRemapping(project, sourceRemapper, artifact.sources().toFile(), info.getRemappedOutput("sources"));
}
modDependencies.add(info);
final ModDependency modDependency = ModDependencyFactory.create(artifact, remappedConfig, clientRemappedConfig, mappingsSuffix, project);
scheduleSourcesRemapping(project, sourceRemapper, modDependency);
modDependencies.add(modDependency);
}
if (modDependencies.isEmpty()) {
// Nothing else to do
return;
}
try {
new ModProcessor(project, sourceConfig).processMods(modDependencies);
} catch (IOException e) {
// Failed to remap, lets clean up to ensure we try again next time
modDependencies.forEach(info -> info.getRemappedOutput().delete());
throw new RuntimeException("Failed to remap mods", e);
final boolean refreshDeps = LoomGradleExtension.get(project).refreshDeps();
final List<ModDependency> toRemap = modDependencies.stream()
.filter(dependency -> refreshDeps || dependency.isCacheInvalid(project, null))
.toList();
if (!toRemap.isEmpty()) {
try {
new ModProcessor(project, sourceConfig).processMods(toRemap);
} catch (IOException e) {
throw new UncheckedIOException("Failed to remap mods", e);
}
}
// Add all of the remapped mods onto the config
for (ModDependencyInfo info : modDependencies) {
project.getDependencies().add(info.targetConfig.getName(), info.getRemappedNotation());
for (ModDependency info : modDependencies) {
info.applyToProject(project);
createConstraints(info.getInputArtifact(), targetConfig, sourceConfig, dependencies);
if (info.getArtifact() instanceof ArtifactRef.ResolvedArtifactRef mavenArtifact) {
final String dependencyCoordinate = "%s:%s".formatted(mavenArtifact.group(), mavenArtifact.name());
// Prevent adding the same un-remapped dependency to the target configuration.
targetConfig.getDependencyConstraints().add(dependencies.getConstraints().create(dependencyCoordinate, constraint -> {
constraint.because("configuration (%s) already contains the remapped module from configuration (%s)".formatted(
targetConfig.getName(),
sourceConfig.getName()
));
constraint.version(MutableVersionConstraint::rejectAll);
}));
if (clientRemappedConfig != null) {
createConstraints(info.getInputArtifact(), entry.getClientTargetConfiguration().get(), sourceConfig, dependencies);
}
}
@@ -147,6 +134,27 @@ public class ModConfigurationRemapper {
}
}
private static void createConstraints(ArtifactRef artifact, Configuration targetConfig, Configuration sourceConfig, DependencyHandler dependencies) {
if (true) {
// Disabled due to the gradle module metadata causing issues. Try the MavenProject test to reproduce issue.
return;
}
if (artifact instanceof ArtifactRef.ResolvedArtifactRef mavenArtifact) {
final String dependencyCoordinate = "%s:%s".formatted(mavenArtifact.group(), mavenArtifact.name());
// Prevent adding the same un-remapped dependency to the target configuration.
targetConfig.getDependencyConstraints().add(dependencies.getConstraints().create(dependencyCoordinate, constraint -> {
constraint.because("configuration (%s) already contains the remapped module from configuration (%s)".formatted(
targetConfig.getName(),
sourceConfig.getName()
));
constraint.version(MutableVersionConstraint::rejectAll);
}));
}
}
private static List<ArtifactRef> resolveArtifacts(Project project, Configuration configuration) {
final List<ArtifactRef> artifacts = new ArrayList<>();
@@ -162,9 +170,8 @@ public class ModConfigurationRemapper {
final FileCollection files = dependency.getFiles();
for (File artifact : files) {
final String name = Files.getNameWithoutExtension(artifact.getAbsolutePath());
final String name = getNameWithoutExtension(artifact.toPath());
final String version = replaceIfNullOrEmpty(dependency.getVersion(), () -> Checksum.truncatedSha256(artifact));
artifacts.add(new ArtifactRef.FileArtifactRef(artifact.toPath(), group, name, version));
}
}
@@ -172,6 +179,12 @@ public class ModConfigurationRemapper {
return artifacts;
}
private static String getNameWithoutExtension(Path file) {
final String fileName = file.getFileName().toString();
final int dotIndex = fileName.lastIndexOf('.');
return (dotIndex == -1) ? fileName : fileName.substring(0, dotIndex);
}
@Nullable
public static Path findSources(Project project, ResolvedArtifact artifact) {
final DependencyHandler dependencies = project.getDependencies();
@@ -191,15 +204,27 @@ public class ModConfigurationRemapper {
return null;
}
private static void scheduleSourcesRemapping(Project project, SourceRemapper sourceRemapper, File input, File output) {
private static void scheduleSourcesRemapping(Project project, SourceRemapper sourceRemapper, ModDependency dependency) {
if (OperatingSystem.isCIBuild()) {
return;
}
if (!output.exists() || input.lastModified() <= 0 || input.lastModified() > output.lastModified() || LoomGradleExtension.get(project).refreshDeps()) {
sourceRemapper.scheduleRemapSources(input, output, false, true); // Depenedency sources are used in ide only so don't need to be reproducable
} else {
project.getLogger().info(output.getName() + " is up to date with " + input.getName());
final Path sourcesInput = dependency.getInputArtifact().sources();
if (sourcesInput == null || Files.notExists(sourcesInput)) {
return;
}
if (dependency.isCacheInvalid(project, "sources")) {
final Path output = dependency.getWorkingFile("sources");
sourceRemapper.scheduleRemapSources(sourcesInput.toFile(), output.toFile(), false, true, () -> {
try {
dependency.copyToCache(project, output, "sources");
} catch (IOException e) {
throw new UncheckedIOException("Failed to apply sources to local cache for: " + dependency, e);
}
});
}
}

View File

@@ -31,7 +31,6 @@ import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
@@ -41,18 +40,15 @@ import java.util.jar.Manifest;
import com.google.gson.JsonObject;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.objectweb.asm.commons.Remapper;
import net.fabricmc.accesswidener.AccessWidenerReader;
import net.fabricmc.accesswidener.AccessWidenerRemapper;
import net.fabricmc.accesswidener.AccessWidenerWriter;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.RemapConfigurationSettings;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.processors.dependency.ModDependencyInfo;
import net.fabricmc.loom.configuration.mods.dependency.ModDependency;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.task.RemapJarTask;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.Pair;
import net.fabricmc.loom.util.TinyRemapperHelper;
import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.loom.util.kotlin.KotlinClasspathService;
@@ -74,75 +70,28 @@ public class ModProcessor {
this.sourceConfiguration = sourceConfiguration;
}
public void processMods(List<ModDependencyInfo> processList) throws IOException {
ArrayList<ModDependencyInfo> remapList = new ArrayList<>();
for (ModDependencyInfo info : processList) {
if (info.requiresRemapping()) {
project.getLogger().debug("{} requires remapping", info.getInputFile());
Files.deleteIfExists(info.getRemappedOutput().toPath());
remapList.add(info);
}
}
if (remapList.isEmpty()) {
project.getLogger().debug("No mods to remap, skipping");
return;
}
public void processMods(List<ModDependency> remapList) throws IOException {
try {
project.getLogger().lifecycle(":remapping {} mods from {}", remapList.size(), sourceConfiguration.getName());
remapJars(remapList);
} catch (Exception e) {
project.getLogger().error(String.format(Locale.ENGLISH, "Failed to remap %d mods", remapList.size()), e);
for (ModDependencyInfo info : remapList) {
Files.deleteIfExists(info.getRemappedOutput().toPath());
}
throw e;
}
// Check all the mods we expect exist
for (ModDependencyInfo info : processList) {
if (!info.getRemappedOutput().exists()) {
throw new RuntimeException("Failed to find remapped mod: " + info);
}
throw new RuntimeException(String.format(Locale.ENGLISH, "Failed to remap %d mods", remapList.size()), e);
}
}
private void stripNestedJars(File file) {
private void stripNestedJars(Path path) {
// Strip out all contained jar info as we dont want loader to try and load the jars contained in dev.
try {
ZipUtils.transformJson(JsonObject.class, file.toPath(), Map.of("fabric.mod.json", json -> {
ZipUtils.transformJson(JsonObject.class, path, Map.of("fabric.mod.json", json -> {
json.remove("jars");
return json;
}));
} catch (IOException e) {
throw new UncheckedIOException("Failed to strip nested jars from %s".formatted(file), e);
throw new UncheckedIOException("Failed to strip nested jars from %s".formatted(path), e);
}
}
/**
* Remap another mod's access widener from intermediary to named, so that loader can apply it in our dev-env.
*/
private byte[] remapAccessWidener(byte[] input, Remapper remapper) {
int version = AccessWidenerReader.readVersion(input);
AccessWidenerWriter writer = new AccessWidenerWriter(version);
AccessWidenerRemapper awRemapper = new AccessWidenerRemapper(
writer,
remapper,
MappingsNamespace.INTERMEDIARY.toString(),
MappingsNamespace.NAMED.toString()
);
AccessWidenerReader reader = new AccessWidenerReader(awRemapper);
reader.read(input);
return writer.write();
}
private void remapJars(List<ModDependencyInfo> remapList) throws IOException {
private void remapJars(List<ModDependency> remapList) throws IOException {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
final MappingsProviderImpl mappingsProvider = extension.getMappingsProvider();
Path[] mcDeps = project.getConfigurations().getByName(Constants.Configurations.LOADER_DEPENDENCIES).getFiles()
@@ -168,13 +117,13 @@ public class ModProcessor {
remapper.readClassPathAsync(mcDeps);
final Map<ModDependencyInfo, InputTag> tagMap = new HashMap<>();
final Map<ModDependencyInfo, OutputConsumerPath> outputConsumerMap = new HashMap<>();
final Map<ModDependencyInfo, byte[]> accessWidenerMap = new HashMap<>();
final Map<ModDependency, InputTag> tagMap = new HashMap<>();
final Map<ModDependency, OutputConsumerPath> outputConsumerMap = new HashMap<>();
final Map<ModDependency, Pair<byte[], String>> accessWidenerMap = new HashMap<>();
for (RemapConfigurationSettings entry : extension.getRemapConfigurations()) {
for (File inputFile : entry.getSourceConfiguration().get().getFiles()) {
if (remapList.stream().noneMatch(info -> info.getInputFile().equals(inputFile))) {
if (remapList.stream().noneMatch(info -> info.getInputFile().toFile().equals(inputFile))) {
project.getLogger().debug("Adding " + inputFile + " onto the remap classpath");
remapper.readClassPathAsync(inputFile.toPath());
@@ -182,35 +131,37 @@ public class ModProcessor {
}
}
for (ModDependencyInfo info : remapList) {
for (ModDependency info : remapList) {
InputTag tag = remapper.createInputTag();
project.getLogger().debug("Adding " + info.getInputFile() + " as a remap input");
remapper.readInputsAsync(tag, info.getInputFile().toPath());
remapper.readInputsAsync(tag, info.getInputFile());
tagMap.put(info, tag);
Files.deleteIfExists(getRemappedOutput(info));
}
try {
// Apply this in a second loop as we need to ensure all the inputs are on the classpath before remapping.
for (ModDependencyInfo info : remapList) {
for (ModDependency dependency : remapList) {
try {
OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(info.getRemappedOutput().toPath()).build();
OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(getRemappedOutput(dependency)).build();
outputConsumer.addNonClassFiles(info.getInputFile().toPath(), NonClassCopyMode.FIX_META_INF, remapper);
outputConsumerMap.put(info, outputConsumer);
outputConsumer.addNonClassFiles(dependency.getInputFile(), NonClassCopyMode.FIX_META_INF, remapper);
outputConsumerMap.put(dependency, outputConsumer);
final ModDependencyInfo.AccessWidenerData accessWidenerData = info.getAccessWidenerData();
final AccessWidenerUtils.AccessWidenerData accessWidenerData = AccessWidenerUtils.readAccessWidenerData(dependency.getInputFile());
if (accessWidenerData != null) {
project.getLogger().debug("Remapping access widener in {}", info.getInputFile());
byte[] remappedAw = remapAccessWidener(accessWidenerData.content(), remapper.getEnvironment().getRemapper());
accessWidenerMap.put(info, remappedAw);
project.getLogger().debug("Remapping access widener in {}", dependency.getInputFile());
byte[] remappedAw = AccessWidenerUtils.remapAccessWidener(accessWidenerData.content(), remapper.getEnvironment().getRemapper());
accessWidenerMap.put(dependency, new Pair<>(remappedAw, accessWidenerData.path()));
}
remapper.apply(outputConsumer, tagMap.get(info));
remapper.apply(outputConsumer, tagMap.get(dependency));
} catch (Exception e) {
throw new RuntimeException("Failed to remap: " + info.getRemappedNotation(), e);
throw new RuntimeException("Failed to remap: " + dependency, e);
}
}
} finally {
@@ -221,22 +172,26 @@ public class ModProcessor {
}
}
for (ModDependencyInfo info : remapList) {
outputConsumerMap.get(info).close();
byte[] accessWidener = accessWidenerMap.get(info);
for (ModDependency dependency : remapList) {
outputConsumerMap.get(dependency).close();
final Path output = getRemappedOutput(dependency);
final Pair<byte[], String> accessWidener = accessWidenerMap.get(dependency);
if (accessWidener != null) {
assert info.getAccessWidenerData() != null;
ZipUtils.replace(info.getRemappedOutput().toPath(), info.getAccessWidenerData().path(), accessWidener);
ZipUtils.replace(output, accessWidener.right(), accessWidener.left());
}
stripNestedJars(info.getRemappedOutput());
remapJarManifestEntries(info.getRemappedOutput().toPath());
info.finaliseRemapping();
stripNestedJars(output);
remapJarManifestEntries(output);
dependency.copyToCache(project, output, null);
}
}
private static Path getRemappedOutput(ModDependency dependency) {
return dependency.getWorkingFile(null);
}
private void remapJarManifestEntries(Path jar) throws IOException {
ZipUtils.transform(jar, Map.of(RemapJarTask.MANIFEST_PATH, bytes -> {
var manifest = new Manifest(new ByteArrayInputStream(bytes));

View File

@@ -0,0 +1,119 @@
/*
* 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.dependency;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import org.gradle.api.Project;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.LoomGradleExtension;
public final class LocalMavenHelper {
private final String group;
private final String name;
private final String version;
@Nullable
private final String baseClassifier;
private final Project project;
LocalMavenHelper(String group, String name, String version, @Nullable String classifier, Project project) {
this.group = group;
this.name = name;
this.version = version;
this.baseClassifier = classifier;
this.project = project;
}
public Path copyToMaven(Path artifact, @Nullable String classifier) throws IOException {
if (!artifact.getFileName().toString().endsWith(".jar")) {
throw new UnsupportedOperationException();
}
Files.createDirectories(getDirectory());
savePom();
return Files.copy(artifact, getOutputFile(classifier), StandardCopyOption.REPLACE_EXISTING);
}
public boolean exists(String classifier) {
return Files.exists(getOutputFile(classifier)) && Files.exists(getPomPath());
}
public String getNotation() {
if (baseClassifier != null) {
return String.format("%s:%s:%s:%s", group, name, version, baseClassifier);
}
return String.format("%s:%s:%s", group, name, version);
}
private void savePom() {
try {
String pomTemplate;
try (InputStream input = ModDependency.class.getClassLoader().getResourceAsStream("mod_compile_template.pom")) {
pomTemplate = new String(input.readAllBytes(), StandardCharsets.UTF_8);
}
pomTemplate = pomTemplate
.replace("%GROUP%", group)
.replace("%NAME%", name)
.replace("%VERSION%", version);
Files.writeString(getPomPath(), pomTemplate, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new UncheckedIOException("Failed to write mod pom", e);
}
}
private Path getRoot() {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
return extension.getFiles().getRemappedModCache().toPath();
}
private Path getDirectory() {
return getRoot().resolve("%s/%s/%s".formatted(group.replace(".", "/"), name, version));
}
private Path getPomPath() {
return getDirectory().resolve("%s-%s.pom".formatted(name, version));
}
public Path getOutputFile(@Nullable String classifier) {
if (classifier == null) {
classifier = baseClassifier;
}
final String fileName = classifier == null ? String.format("%s-%s.jar", name, version)
: String.format("%s-%s-%s.jar", name, version, classifier);
return getDirectory().resolve(fileName);
}
}

View File

@@ -0,0 +1,103 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2020-2021 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.mods.dependency;
import java.io.IOException;
import java.nio.file.Path;
import org.gradle.api.Project;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.mods.ArtifactRef;
public abstract sealed class ModDependency permits SplitModDependency, SimpleModDependency {
private final ArtifactRef artifact;
protected final String group;
protected final String name;
protected final String version;
@Nullable
protected final String classifier;
protected final String mappingsSuffix;
protected final Project project;
public ModDependency(ArtifactRef artifact, String mappingsSuffix, Project project) {
this.artifact = artifact;
this.group = artifact.group();
this.name = artifact.name();
this.version = artifact.version();
this.classifier = artifact.classifier();
this.mappingsSuffix = mappingsSuffix;
this.project = project;
}
/**
* Returns true when the cache is invalid.
*/
public abstract boolean isCacheInvalid(Project project, @Nullable String variant);
/**
* Write an artifact to the local cache.
*/
public abstract void copyToCache(Project project, Path path, @Nullable String variant) throws IOException;
/**
* Apply the dependency to the project.
*/
public abstract void applyToProject(Project project);
protected LocalMavenHelper createMaven(String name) {
return new LocalMavenHelper(getRemappedGroup(), name, this.version, this.classifier, this.project);
}
public ArtifactRef getInputArtifact() {
return artifact;
}
protected String getRemappedGroup() {
return getMappingsPrefix() + "." + group;
}
private String getMappingsPrefix() {
return mappingsSuffix.replace(".", "_").replace("-", "_").replace("+", "_");
}
public Path getInputFile() {
return artifact.path();
}
public Path getWorkingFile(@Nullable String classifier) {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
final String fileName = classifier == null ? String.format("%s-%s-%s.jar", getRemappedGroup(), name, version)
: String.format("%s-%s-%s-%s.jar", getRemappedGroup(), name, version, classifier);
return extension.getFiles().getProjectBuildCache().toPath().resolve("remapped_working").resolve(fileName);
}
@Override
public String toString() {
return "ModDependency{" + "group='" + group + '\'' + ", name='" + name + '\'' + ", version='" + version + '\'' + ", classifier='" + classifier + '\'' + '}';
}
}

View File

@@ -0,0 +1,86 @@
/*
* 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.dependency;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Optional;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.mods.ArtifactRef;
import net.fabricmc.loom.configuration.mods.JarSplitter;
import net.fabricmc.loom.util.AttributeHelper;
public class ModDependencyFactory {
private static final String TARGET_ATTRIBUTE_KEY = "loom-target";
public static ModDependency create(ArtifactRef artifact, Configuration targetConfig, @Nullable Configuration targetClientConfig, String mappingsSuffix, Project project) {
if (targetClientConfig != null && LoomGradleExtension.get(project).getSplitModDependencies().get()) {
final Optional<JarSplitter.Target> cachedTarget = readTarget(artifact);
JarSplitter.Target target;
if (cachedTarget.isPresent()) {
target = cachedTarget.get();
} else {
target = new JarSplitter(artifact.path()).analyseTarget();
writeTarget(artifact, target);
}
if (target != null) {
return new SplitModDependency(artifact, mappingsSuffix, targetConfig, targetClientConfig, target, project);
}
}
return new SimpleModDependency(artifact, mappingsSuffix, targetConfig, project);
}
private static Optional<JarSplitter.Target> readTarget(ArtifactRef artifact) {
try {
return AttributeHelper.readAttribute(artifact.path(), TARGET_ATTRIBUTE_KEY).map(s -> {
if ("null".equals(s)) {
return null;
}
return JarSplitter.Target.valueOf(s);
});
} catch (IOException e) {
throw new UncheckedIOException("Failed to read artifact target attribute", e);
}
}
private static void writeTarget(ArtifactRef artifact, JarSplitter.Target target) {
final String value = target != null ? target.name() : "null";
try {
AttributeHelper.writeAttribute(artifact.path(), TARGET_ATTRIBUTE_KEY, value);
} catch (IOException e) {
throw new UncheckedIOException("Failed to write artifact target attribute", e);
}
}
}

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.configuration.mods.dependency;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Objects;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.configuration.mods.ArtifactRef;
// Single jar in and out
public final class SimpleModDependency extends ModDependency {
private final Configuration targetConfig;
private final LocalMavenHelper maven;
public SimpleModDependency(ArtifactRef artifact, String mappingsSuffix, Configuration targetConfig, Project project) {
super(artifact, mappingsSuffix, project);
this.targetConfig = Objects.requireNonNull(targetConfig);
this.maven = createMaven(name);
}
@Override
public boolean isCacheInvalid(Project project, @Nullable String variant) {
return !maven.exists(variant);
}
@Override
public void copyToCache(Project project, Path path, @Nullable String variant) throws IOException {
maven.copyToMaven(path, variant);
}
@Override
public void applyToProject(Project project) {
project.getDependencies().add(targetConfig.getName(), maven.getNotation());
}
}

View File

@@ -0,0 +1,138 @@
/*
* 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.dependency;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Objects;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.mods.ArtifactRef;
import net.fabricmc.loom.configuration.mods.JarSplitter;
// Single jar in, 2 out.
public final class SplitModDependency extends ModDependency {
private final Configuration targetCommonConfig;
private final Configuration targetClientConfig;
private final JarSplitter.Target target;
@Nullable
private final LocalMavenHelper commonMaven;
@Nullable
private final LocalMavenHelper clientMaven;
public SplitModDependency(ArtifactRef artifact, String mappingsSuffix, Configuration targetCommonConfig, Configuration targetClientConfig, JarSplitter.Target target, Project project) {
super(artifact, mappingsSuffix, project);
this.targetCommonConfig = Objects.requireNonNull(targetCommonConfig);
this.targetClientConfig = Objects.requireNonNull(targetClientConfig);
this.target = Objects.requireNonNull(target);
this.commonMaven = target.common() ? createMaven(name + "-common") : null;
this.clientMaven = target.client() ? createMaven(name + "-client") : null;
}
@Override
public boolean isCacheInvalid(Project project, @Nullable String variant) {
boolean exists = switch (target) {
case COMMON_ONLY -> getCommonMaven().exists(variant);
case CLIENT_ONLY -> getClientMaven().exists(variant);
case SPLIT -> getCommonMaven().exists(variant) && getClientMaven().exists(variant);
};
return !exists;
}
@Override
public void copyToCache(Project project, Path path, @Nullable String variant) throws IOException {
// Split dependencies build with loom 0.12 do not contain the required data to split the sources
if (target == JarSplitter.Target.SPLIT && variant != null) {
final JarSplitter.Target artifactTarget = new JarSplitter(path).analyseTarget();
if (artifactTarget != target) {
// Found a broken artifact, copy it to both locations without splitting.
getCommonMaven().copyToMaven(path, variant);
getClientMaven().copyToMaven(path, variant);
return;
}
}
switch (target) {
// Split the jar into 2
case SPLIT -> {
final String suffix = variant == null ? "" : "-" + variant;
final Path commonTempJar = getWorkingFile("common" + suffix);
final Path clientTempJar = getWorkingFile("client" + suffix);
final JarSplitter splitter = new JarSplitter(path);
splitter.split(commonTempJar, clientTempJar);
getCommonMaven().copyToMaven(commonTempJar, variant);
getClientMaven().copyToMaven(clientTempJar, variant);
}
// No splitting to be done, just copy the input jar to the respective location.
case CLIENT_ONLY -> getClientMaven().copyToMaven(path, variant);
case COMMON_ONLY -> getCommonMaven().copyToMaven(path, variant);
}
}
@Override
public void applyToProject(Project project) {
if (target.common()) {
project.getDependencies().add(targetCommonConfig.getName(), getCommonMaven().getNotation());
}
if (target.client()) {
project.getDependencies().add(targetClientConfig.getName(), getClientMaven().getNotation());
}
if (target == JarSplitter.Target.SPLIT) {
createModGroup(
getCommonMaven().getOutputFile(null),
getClientMaven().getOutputFile(null)
);
}
}
private void createModGroup(Path commonJar, Path clientJar) {
LoomGradleExtension extension = LoomGradleExtension.get(project);
extension.getMods().register(String.format("%s-%s-%s", getRemappedGroup(), name, version), modSettings ->
modSettings.getModFiles().from(
commonJar.toFile(),
clientJar.toFile()
)
);
}
public LocalMavenHelper getCommonMaven() {
return Objects.requireNonNull(commonMaven, "Cannot get null common maven helper");
}
public LocalMavenHelper getClientMaven() {
return Objects.requireNonNull(clientMaven, "Cannot get null client maven helper");
}
}

View File

@@ -22,7 +22,7 @@
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.mods;
package net.fabricmc.loom.configuration.processors;
import java.io.ByteArrayInputStream;
import java.io.File;
@@ -44,7 +44,6 @@ import org.slf4j.LoggerFactory;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.RemapConfigurationSettings;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
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;

View File

@@ -1,221 +0,0 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2020-2021 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.processors.dependency;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import com.google.gson.JsonObject;
import org.apache.commons.io.FileUtils;
import org.gradle.api.artifacts.Configuration;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.accesswidener.AccessWidenerReader;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.mods.ArtifactRef;
import net.fabricmc.loom.util.ZipUtils;
public class ModDependencyInfo {
private final ArtifactRef artifact;
private final String group;
public final String name;
public final String version;
@Nullable
public final String classifier;
public final Configuration targetConfig;
@Nullable
public final Configuration targetClientConfig;
public final RemapData remapData;
@Nullable
private final AccessWidenerData accessWidenerData;
private boolean forceRemap = false;
public ModDependencyInfo(ArtifactRef artifact, Configuration targetConfig, @Nullable Configuration targetClientConfig, RemapData remapData) {
this.artifact = artifact;
this.group = artifact.group();
this.name = artifact.name();
this.version = artifact.version();
this.classifier = artifact.classifier();
this.targetConfig = targetConfig;
this.targetClientConfig = targetClientConfig;
this.remapData = remapData;
try {
this.accessWidenerData = readAccessWidenerData(artifact.path());
} catch (IOException e) {
throw new UncheckedIOException("Failed to read access widener data from" + artifact.path(), e);
}
}
public ArtifactRef getArtifact() {
return artifact;
}
public String getRemappedNotation() {
if (!hasClassifier()) {
return String.format("%s:%s:%s", getGroup(), name, version);
}
return String.format("%s:%s:%s:%s", getGroup(), name, version, classifier);
}
public String getRemappedFilename(boolean withClassifier) {
if (!hasClassifier() || !withClassifier) {
return String.format("%s-%s", name, version);
}
return String.format("%s-%s-%s", name, version, classifier);
}
public File getRemappedDir() {
return new File(remapData.modStore(), String.format("%s/%s/%s", getGroup().replace(".", "/"), name, version));
}
public File getRemappedOutput() {
return new File(getRemappedDir(), getRemappedFilename(true) + ".jar");
}
public File getRemappedOutput(String classifier) {
return new File(getRemappedDir(), getRemappedFilename(false) + "-" + classifier + ".jar");
}
private File getRemappedPom() {
return new File(getRemappedDir(), String.format("%s-%s", name, version) + ".pom");
}
private String getGroup() {
return getMappingsPrefix(remapData.mappingsSuffix()) + "." + group;
}
public static String getMappingsPrefix(String mappings) {
return mappings.replace(".", "_").replace("-", "_").replace("+", "_");
}
public File getInputFile() {
return artifact.path().toFile();
}
private boolean outputHasInvalidAccessWidener() {
if (accessWidenerData == null) {
// This mod doesn't use an AW
return false;
}
assert getRemappedOutput().exists();
final AccessWidenerData outputAWData;
try {
outputAWData = readAccessWidenerData(getRemappedOutput().toPath());
} catch (IOException e) {
throw new UncheckedIOException("Failed to read output access widener data from " + getRemappedOutput(), e);
}
if (outputAWData == null) {
// We know for sure the input has an AW, something is wrong if the output hasn't got one.
return true;
}
// The output jar must have an AW in the "named" namespace.
return !MappingsNamespace.NAMED.toString().equals(outputAWData.header().getNamespace());
}
public boolean requiresRemapping() {
final File inputFile = artifact.path().toFile();
final long lastModified = inputFile.lastModified();
return !getRemappedOutput().exists() || lastModified <= 0 || lastModified > getRemappedOutput().lastModified() || forceRemap || !getRemappedPom().exists() || outputHasInvalidAccessWidener();
}
public void finaliseRemapping() {
getRemappedOutput().setLastModified(artifact.path().toFile().lastModified());
savePom();
// Validate that the remapped AW is what we want.
if (outputHasInvalidAccessWidener()) {
throw new RuntimeException("Failed to validate remapped access widener in " + getRemappedOutput());
}
}
private void savePom() {
try {
String pomTemplate;
try (InputStream input = ModDependencyInfo.class.getClassLoader().getResourceAsStream("mod_compile_template.pom")) {
pomTemplate = new String(input.readAllBytes(), StandardCharsets.UTF_8);
}
pomTemplate = pomTemplate
.replace("%GROUP%", getGroup())
.replace("%NAME%", name)
.replace("%VERSION%", version);
FileUtils.writeStringToFile(getRemappedPom(), pomTemplate, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new RuntimeException("Failed to write mod pom", e);
}
}
public void forceRemap() {
forceRemap = true;
}
@Override
public String toString() {
return getRemappedNotation();
}
public boolean hasClassifier() {
return classifier != null && !classifier.isEmpty();
}
@Nullable
public AccessWidenerData getAccessWidenerData() {
return accessWidenerData;
}
private static AccessWidenerData readAccessWidenerData(Path inputJar) throws IOException {
byte[] modJsonBytes = ZipUtils.unpack(inputJar, "fabric.mod.json");
JsonObject jsonObject = LoomGradlePlugin.GSON.fromJson(new String(modJsonBytes, StandardCharsets.UTF_8), JsonObject.class);
if (!jsonObject.has("accessWidener")) {
return null;
}
String accessWidenerPath = jsonObject.get("accessWidener").getAsString();
byte[] accessWidener = ZipUtils.unpack(inputJar, accessWidenerPath);
AccessWidenerReader.Header header = AccessWidenerReader.readHeader(accessWidener);
return new AccessWidenerData(accessWidenerPath, header, accessWidener);
}
public record AccessWidenerData(String path, AccessWidenerReader.Header header, byte[] content) {
}
}

View File

@@ -1,30 +0,0 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2016-2021 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.processors.dependency;
import java.io.File;
public record RemapData(String mappingsSuffix, File modStore) {
}

View File

@@ -46,7 +46,6 @@ import net.fabricmc.loom.api.mappings.intermediate.IntermediateMappingsProvider;
import net.fabricmc.loom.api.mappings.layered.spec.LayeredMappingSpecBuilder;
import net.fabricmc.loom.configuration.RemapConfigurations;
import net.fabricmc.loom.configuration.ide.RunConfigSettings;
import net.fabricmc.loom.configuration.mods.ModVersionParser;
import net.fabricmc.loom.configuration.processors.JarProcessor;
import net.fabricmc.loom.configuration.providers.mappings.GradleMappingContext;
import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingSpec;
@@ -71,6 +70,7 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
protected final Property<String> intermediary;
protected final Property<IntermediateMappingsProvider> intermediateMappingsProvider;
private final Property<Boolean> runtimeOnlyLog4j;
private final Property<Boolean> splitModDependencies;
private final Property<MinecraftJarConfiguration> minecraftJarConfiguration;
private final Property<Boolean> splitEnvironmentalSourceSet;
private final InterfaceInjectionExtensionAPI interfaceInjectionExtension;
@@ -122,6 +122,9 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
this.runtimeOnlyLog4j = project.getObjects().property(Boolean.class).convention(false);
this.runtimeOnlyLog4j.finalizeValueOnRead();
this.splitModDependencies = project.getObjects().property(Boolean.class).convention(true);
this.splitModDependencies.finalizeValueOnRead();
this.interfaceInjectionExtension = project.getObjects().newInstance(InterfaceInjectionExtensionAPI.class);
this.splitEnvironmentalSourceSet = project.getObjects().property(Boolean.class).convention(false);
@@ -267,6 +270,11 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
return runtimeOnlyLog4j;
}
@Override
public Property<Boolean> getSplitModDependencies() {
return splitModDependencies;
}
@Override
public void splitEnvironmentSourceSets() {
splitMinecraftJar();

View File

@@ -232,7 +232,7 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
builder.offline();
}
if (project.getGradle().getStartParameter().isRefreshDependencies() || Boolean.getBoolean("loom.refresh")) {
if (refreshDeps()) {
builder.forceDownload();
}

View File

@@ -22,7 +22,7 @@
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.mods;
package net.fabricmc.loom.extension;
import java.io.File;
import java.io.FileReader;

View File

@@ -64,7 +64,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.configuration.processors.ModJavadocProcessor;
import net.fabricmc.loom.decompilers.LineNumberRemapper;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.FileSystemUtil;

View File

@@ -39,6 +39,7 @@ import net.fabricmc.loom.task.launch.GenerateDLIConfigTask;
import net.fabricmc.loom.task.launch.GenerateLog4jConfigTask;
import net.fabricmc.loom.task.launch.GenerateRemapClasspathTask;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.gradle.GradleUtils;
public final class LoomTasks {
private LoomTasks() {
@@ -87,7 +88,7 @@ public final class LoomTasks {
registerRunTasks(tasks, project);
// Must be done in afterEvaluate to allow time for the build script to configure the jar config.
project.afterEvaluate(p -> {
GradleUtils.afterSuccessfulEvaluation(project, () -> {
LoomGradleExtension extension = LoomGradleExtension.get(project);
if (extension.getMinecraftJarConfiguration().get() == MinecraftJarConfiguration.SERVER_ONLY) {
@@ -150,7 +151,7 @@ public final class LoomTasks {
extension.getRunConfigs().create("server", RunConfigSettings::server);
// Remove the client or server run config when not required. Done by name to not remove any possible custom run configs
project.afterEvaluate(p -> {
GradleUtils.afterSuccessfulEvaluation(project, () -> {
String taskName = switch (extension.getMinecraftJarConfiguration().get()) {
case SERVER_ONLY -> "client";
case CLIENT_ONLY -> "server";

View File

@@ -38,6 +38,7 @@ import org.gradle.api.tasks.bundling.Jar;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.gradle.GradleUtils;
import net.fabricmc.loom.util.gradle.SourceSetHelper;
public class RemapTaskConfiguration {
@@ -84,7 +85,7 @@ public class RemapTaskConfiguration {
return;
}
project.afterEvaluate(p -> {
GradleUtils.afterSuccessfulEvaluation(project, () -> {
// Remove -dev jars from the default jar task
for (String configurationName : new String[] { JavaPlugin.API_ELEMENTS_CONFIGURATION_NAME, JavaPlugin.RUNTIME_ELEMENTS_CONFIGURATION_NAME }) {
Configuration configuration = project.getConfigurations().getByName(configurationName);
@@ -135,7 +136,7 @@ public class RemapTaskConfiguration {
return;
}
project.afterEvaluate(p -> {
GradleUtils.afterSuccessfulEvaluation(project, () -> {
final Task sourcesTask = project.getTasks().findByName(sourcesJarTaskName);
if (!(sourcesTask instanceof Jar sourcesJarTask)) {

View File

@@ -0,0 +1,61 @@
/*
* 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.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.UserDefinedFileAttributeView;
import java.util.Optional;
public final class AttributeHelper {
private AttributeHelper() {
}
public static Optional<String> readAttribute(Path path, String key) throws IOException {
final UserDefinedFileAttributeView attributeView = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
if (!attributeView.list().contains(key)) {
return Optional.empty();
}
final ByteBuffer buffer = ByteBuffer.allocate(attributeView.size(key));
attributeView.read(key, buffer);
buffer.flip();
final String value = StandardCharsets.UTF_8.decode(buffer).toString();
return Optional.of(value);
}
public static void writeAttribute(Path path, String key, String value) throws IOException {
// TODO may need to fallback to creating a separate file if this isnt supported.
final UserDefinedFileAttributeView attributeView = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
final byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
final ByteBuffer buffer = ByteBuffer.wrap(bytes);
final int written = attributeView.write(key, buffer);
assert written == bytes.length;
}
}

View File

@@ -61,13 +61,7 @@ public class SourceRemapper {
this.toNamed = toNamed;
}
public static void remapSources(Project project, File input, File output, boolean named, boolean reproducibleFileOrder, boolean preserveFileTimestamps) {
SourceRemapper sourceRemapper = new SourceRemapper(project, named);
sourceRemapper.scheduleRemapSources(input, output, reproducibleFileOrder, preserveFileTimestamps);
sourceRemapper.remapAll();
}
public void scheduleRemapSources(File source, File destination, boolean reproducibleFileOrder, boolean preserveFileTimestamps) {
public void scheduleRemapSources(File source, File destination, boolean reproducibleFileOrder, boolean preserveFileTimestamps, Runnable completionCallback) {
remapTasks.add((logger) -> {
try {
logger.progress("remapping sources - " + source.getName());
@@ -76,6 +70,7 @@ public class SourceRemapper {
// Set the remapped sources creation date to match the sources if we're likely succeeded in making it
destination.setLastModified(source.lastModified());
completionCallback.run();
} catch (Exception e) {
// Failed to remap, lets clean up to ensure we try again next time
destination.delete();

View File

@@ -34,14 +34,12 @@ import java.net.http.HttpClient;
import java.net.http.HttpHeaders;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.UserDefinedFileAttributeView;
import java.time.Duration;
import java.time.Instant;
import java.util.Locale;
@@ -53,6 +51,7 @@ import com.github.mizosoft.methanol.ProgressTracker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.loom.util.AttributeHelper;
import net.fabricmc.loom.util.Checksum;
public class Download {
@@ -226,6 +225,11 @@ public class Download {
}
private boolean requiresDownload(Path output) throws DownloadException {
if (getAndResetLock(output)) {
LOGGER.warn("Forcing downloading {} as existing lock file was found. This may happen if the gradle build was forcefully canceled.", output);
return true;
}
if (forceDownload || !exists(output)) {
// File does not exist, or we are forced to download again.
return true;
@@ -236,11 +240,6 @@ public class Download {
return false;
}
if (getAndResetLock(output)) {
LOGGER.warn("Forcing downloading {} as existing lock file was found. This may happen if the gradle build was forcefully canceled.", output);
return true;
}
if (expectedHash != null) {
final String hashAttribute = readHash(output).orElse("");
@@ -301,7 +300,7 @@ public class Download {
private Optional<String> readEtag(Path output) {
try {
return readAttribute(output, E_TAG);
return AttributeHelper.readAttribute(output, E_TAG);
} catch (IOException e) {
return Optional.empty();
}
@@ -309,7 +308,7 @@ public class Download {
private void writeEtag(Path output, String eTag) throws DownloadException {
try {
writeAttribute(output, E_TAG, eTag);
AttributeHelper.writeAttribute(output, E_TAG, eTag);
} catch (IOException e) {
throw error(e, "Failed to write etag to (%s)", output);
}
@@ -317,7 +316,7 @@ public class Download {
private Optional<String> readHash(Path output) {
try {
return readAttribute(output, "LoomHash");
return AttributeHelper.readAttribute(output, "LoomHash");
} catch (IOException e) {
return Optional.empty();
}
@@ -325,7 +324,7 @@ public class Download {
private void writeHash(Path output, String eTag) throws DownloadException {
try {
writeAttribute(output, "LoomHash", eTag);
AttributeHelper.writeAttribute(output, "LoomHash", eTag);
} catch (IOException e) {
throw error(e, "Failed to write hash to (%s)", output);
}
@@ -344,29 +343,6 @@ public class Download {
return path.getFileSystem() == FileSystems.getDefault() ? path.toFile().exists() : Files.exists(path);
}
private static Optional<String> readAttribute(Path path, String key) throws IOException {
final UserDefinedFileAttributeView attributeView = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
if (!attributeView.list().contains(key)) {
return Optional.empty();
}
final ByteBuffer buffer = ByteBuffer.allocate(attributeView.size(key));
attributeView.read(key, buffer);
buffer.flip();
final String value = StandardCharsets.UTF_8.decode(buffer).toString();
return Optional.of(value);
}
private static void writeAttribute(Path path, String key, String value) throws IOException {
// TODO may need to fallback to creating a separate file if this isnt supported.
final UserDefinedFileAttributeView attributeView = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
final byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
final ByteBuffer buffer = ByteBuffer.wrap(bytes);
final int written = attributeView.write(key, buffer);
assert written == bytes.length;
}
private FileTime getLastModified(Path path) throws IOException {
final BasicFileAttributeView basicView = Files.getFileAttributeView(path, BasicFileAttributeView.class);
return basicView.readAttributes().lastModifiedTime();
@@ -380,10 +356,12 @@ public class Download {
final Path lock = getLockFile(output);
final boolean exists = Files.exists(lock);
try {
Files.deleteIfExists(lock);
} catch (IOException e) {
throw error(e, "Failed to release lock on %s", lock);
if (exists) {
try {
Files.delete(lock);
} catch (IOException e) {
throw error(e, "Failed to release lock on %s", lock);
}
}
return exists;

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
@@ -24,14 +24,21 @@
package net.fabricmc.loom.util.gradle;
import org.gradle.util.GradleVersion;
import org.gradle.api.Project;
// This is used to bridge the gap over large gradle api changes.
public class GradleSupport {
public static final boolean IS_GRADLE_7_OR_NEWER = isIsGradle7OrNewer();
public final class GradleUtils {
private GradleUtils() {
}
public static boolean isIsGradle7OrNewer() {
String version = GradleVersion.current().getVersion();
return Integer.parseInt(version.substring(0, version.indexOf("."))) >= 7;
// For some crazy reason afterEvaluate is still invoked when the configuration fails
public static void afterSuccessfulEvaluation(Project project, Runnable afterEvaluate) {
project.afterEvaluate(p -> {
if (p.getState().getFailure() != null) {
// Let gradle handle the failure
return;
}
afterEvaluate.run();
});
}
}