New service system for source jar remapping (#1160)

* New service system for source jar remapping

* Remove the class generation, its too complex and useless.

* Fix

* Tests and cleanup

* Add basic SourceRemapperService test

* Skip file property tests on windows.
This commit is contained in:
modmuss
2024-08-19 15:18:13 +01:00
committed by GitHub
parent 4fef156888
commit 3c32259001
16 changed files with 1226 additions and 66 deletions

View File

@@ -33,34 +33,33 @@ import javax.inject.Inject;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.Nested;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.TaskAction;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.loom.task.service.SourceRemapperService;
import net.fabricmc.loom.util.service.BuildSharedServiceManager;
import net.fabricmc.loom.util.service.UnsafeWorkQueueHelper;
import net.fabricmc.loom.util.newService.ScopedServiceFactory;
public abstract class RemapSourcesJarTask extends AbstractRemapJarTask {
private final Provider<BuildSharedServiceManager> serviceManagerProvider;
@Nested
abstract Property<SourceRemapperService.Options> getSourcesRemapperServiceOptions();
@Inject
public RemapSourcesJarTask() {
super();
serviceManagerProvider = BuildSharedServiceManager.createForTask(this, getBuildEventsListenerRegistry());
getClasspath().from(getProject().getConfigurations().getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME));
getJarType().set("sources");
getSourcesRemapperServiceOptions().set(SourceRemapperService.createOptions(this));
}
@TaskAction
public void run() {
submitWork(RemapSourcesAction.class, params -> {
if (!params.namespacesMatch()) {
params.getSourcesRemapperServiceUuid().set(UnsafeWorkQueueHelper.create(SourceRemapperService.create(serviceManagerProvider.get().get(), this)));
params.getSourcesRemapperServiceOptions().set(getSourcesRemapperServiceOptions());
}
});
}
@@ -73,27 +72,24 @@ public abstract class RemapSourcesJarTask extends AbstractRemapJarTask {
}
public interface RemapSourcesParams extends AbstractRemapParams {
Property<String> getSourcesRemapperServiceUuid();
Property<SourceRemapperService.Options> getSourcesRemapperServiceOptions();
}
public abstract static class RemapSourcesAction extends AbstractRemapAction<RemapSourcesParams> {
private static final Logger LOGGER = LoggerFactory.getLogger(RemapSourcesAction.class);
private final @Nullable SourceRemapperService sourceRemapperService;
public RemapSourcesAction() {
super();
sourceRemapperService = getParameters().getSourcesRemapperServiceUuid().isPresent()
? UnsafeWorkQueueHelper.get(getParameters().getSourcesRemapperServiceUuid(), SourceRemapperService.class)
: null;
}
@Override
public void execute() {
try {
if (sourceRemapperService != null) {
sourceRemapperService.remapSourcesJar(inputFile, outputFile);
if (!getParameters().namespacesMatch()) {
try (var serviceFactory = new ScopedServiceFactory()) {
SourceRemapperService sourceRemapperService = serviceFactory.get(getParameters().getSourcesRemapperServiceOptions());
sourceRemapperService.remapSourcesJar(inputFile, outputFile);
}
} else {
Files.copy(inputFile, outputFile, StandardCopyOption.REPLACE_EXISTING);
}

View File

@@ -0,0 +1,140 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2021 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.task.service;
import java.io.Closeable;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import org.gradle.api.Project;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration;
import net.fabricmc.loom.util.TinyRemapperHelper;
import net.fabricmc.loom.util.newService.Service;
import net.fabricmc.loom.util.newService.ServiceFactory;
import net.fabricmc.loom.util.newService.ServiceType;
import net.fabricmc.mappingio.MappingReader;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
import net.fabricmc.tinyremapper.IMappingProvider;
/**
* A service that provides mappings for remapping.
*/
public final class NewMappingsService extends Service<NewMappingsService.Options> implements Closeable {
public static ServiceType<Options, NewMappingsService> TYPE = new ServiceType<>(Options.class, NewMappingsService.class);
public interface Options extends Service.Options {
@InputFile
RegularFileProperty getMappingsFile();
@Input
Property<String> getFrom();
@Input
Property<String> getTo();
@Input
Property<Boolean> getRemapLocals();
}
/**
* Returns options for creating a new mappings service, with a given mappings file.
*/
public static Provider<Options> createOptions(Project project, Path mappingsFile, String from, String to, boolean remapLocals) {
return TYPE.create(project, o -> {
o.getMappingsFile().set(project.file(mappingsFile));
o.getFrom().set(from);
o.getTo().set(to);
o.getRemapLocals().set(remapLocals);
});
}
/**
* Returns options for creating a new mappings service, using the mappings as specified in the project's mapping configuration.
*/
public static Provider<Options> createOptionsWithProjectMappings(Project project, String from, String to) {
final MappingConfiguration mappingConfiguration = LoomGradleExtension.get(project).getMappingConfiguration();
return createOptions(project, mappingConfiguration.tinyMappings, from, to, false);
}
public NewMappingsService(Options options, ServiceFactory serviceFactory) {
super(options, serviceFactory);
}
private IMappingProvider mappingProvider = null;
private MemoryMappingTree memoryMappingTree = null;
public IMappingProvider getMappingsProvider() {
if (mappingProvider == null) {
try {
mappingProvider = TinyRemapperHelper.create(
getMappingsPath(),
getFrom(),
getTo(),
getOptions().getRemapLocals().get()
);
} catch (IOException e) {
throw new UncheckedIOException("Failed to read mappings from: " + getMappingsPath(), e);
}
}
return mappingProvider;
}
public MemoryMappingTree getMemoryMappingTree() {
if (memoryMappingTree == null) {
memoryMappingTree = new MemoryMappingTree();
try {
MappingReader.read(getMappingsPath(), memoryMappingTree);
} catch (IOException e) {
throw new UncheckedIOException("Failed to read mappings from: " + getMappingsPath(), e);
}
}
return memoryMappingTree;
}
public String getFrom() {
return getOptions().getFrom().get();
}
public String getTo() {
return getOptions().getTo().get();
}
public Path getMappingsPath() {
return getOptions().getMappingsFile().get().getAsFile().toPath();
}
@Override
public void close() {
mappingProvider = null;
}
}

View File

@@ -26,55 +26,58 @@ package net.fabricmc.loom.task.service;
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.function.Supplier;
import com.google.common.base.Suppliers;
import org.cadixdev.lorenz.MappingSet;
import org.cadixdev.mercury.Mercury;
import org.cadixdev.mercury.remapper.MercuryRemapper;
import org.gradle.api.Project;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.Nested;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.task.RemapSourcesJarTask;
import net.fabricmc.loom.util.DeletingFileVisitor;
import net.fabricmc.loom.util.FileSystemUtil;
import net.fabricmc.loom.util.SourceRemapper;
import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.loom.util.service.SharedService;
import net.fabricmc.loom.util.service.SharedServiceManager;
import net.fabricmc.loom.util.newService.Service;
import net.fabricmc.loom.util.newService.ServiceFactory;
import net.fabricmc.loom.util.newService.ServiceType;
import net.fabricmc.lorenztiny.TinyMappingsReader;
public final class SourceRemapperService implements SharedService {
public static synchronized SourceRemapperService create(SharedServiceManager serviceManager, RemapSourcesJarTask task) {
final Project project = task.getProject();
final String to = task.getTargetNamespace().get();
final String from = task.getSourceNamespace().get();
final LoomGradleExtension extension = LoomGradleExtension.get(project);
final String id = extension.getMappingConfiguration().getBuildServiceName("sourceremapper", from, to);
final int javaCompileRelease = SourceRemapper.getJavaCompileRelease(project);
public final class SourceRemapperService extends Service<SourceRemapperService.Options> {
public static ServiceType<Options, SourceRemapperService> TYPE = new ServiceType<>(Options.class, SourceRemapperService.class);
return serviceManager.getOrCreateService(id, () ->
new SourceRemapperService(MappingsService.createDefault(project, serviceManager, from, to), task.getClasspath(), javaCompileRelease));
public interface Options extends Service.Options {
@Nested
Property<NewMappingsService.Options> getMappings();
@Input
Property<Integer> getJavaCompileRelease();
@InputFiles
ConfigurableFileCollection getClasspath();
}
public static Provider<Options> createOptions(RemapSourcesJarTask task) {
return TYPE.create(task.getProject(), o -> {
o.getMappings().set(NewMappingsService.createOptionsWithProjectMappings(
task.getProject(),
task.getSourceNamespace().get(),
task.getTargetNamespace().get()
));
o.getJavaCompileRelease().set(SourceRemapper.getJavaCompileRelease(task.getProject()));
o.getClasspath().from(task.getClasspath());
});
}
private static final Logger LOGGER = LoggerFactory.getLogger(SourceRemapperService.class);
private final MappingsService mappingsService;
private final ConfigurableFileCollection classpath;
private final int javaCompileRelease;
private final Supplier<Mercury> mercury = Suppliers.memoize(this::createMercury);
private SourceRemapperService(MappingsService mappingsService, ConfigurableFileCollection classpath, int javaCompileRelease) {
this.mappingsService = mappingsService;
this.classpath = classpath;
this.javaCompileRelease = javaCompileRelease;
public SourceRemapperService(Options options, ServiceFactory serviceFactory) {
super(options, serviceFactory);
}
public void remapSourcesJar(Path source, Path destination) throws IOException {
@@ -96,10 +99,17 @@ public final class SourceRemapperService implements SharedService {
Files.delete(destination);
}
Mercury mercury = createMercury();
try (FileSystemUtil.Delegate dstFs = Files.isDirectory(destination) ? null : FileSystemUtil.getJarFileSystem(destination, true)) {
Path dstPath = dstFs != null ? dstFs.get().getPath("/") : destination;
doRemap(srcPath, dstPath, source);
try {
mercury.rewrite(srcPath, dstPath);
} catch (Exception e) {
LOGGER.warn("Could not remap " + source + " fully!", e);
}
SourceRemapper.copyNonJavaFiles(srcPath, dstPath, LOGGER, source);
} finally {
if (isSrcTmp) {
@@ -108,30 +118,16 @@ public final class SourceRemapperService implements SharedService {
}
}
private synchronized void doRemap(Path srcPath, Path dstPath, Path source) {
try {
mercury.get().rewrite(srcPath, dstPath);
} catch (Exception e) {
LOGGER.warn("Could not remap " + source + " fully!", e);
}
}
private MappingSet getMappings() throws IOException {
return new TinyMappingsReader(mappingsService.getMemoryMappingTree(), mappingsService.getFromNamespace(), mappingsService.getToNamespace()).read();
}
private Mercury createMercury() {
private Mercury createMercury() throws IOException {
var mercury = new Mercury();
mercury.setGracefulClasspathChecks(true);
mercury.setSourceCompatibilityFromRelease(javaCompileRelease);
mercury.setSourceCompatibilityFromRelease(getOptions().getJavaCompileRelease().get());
try {
mercury.getProcessors().add(MercuryRemapper.create(getMappings()));
} catch (IOException e) {
throw new UncheckedIOException("Failed to read mercury mappings", e);
}
NewMappingsService mappingsService = getServiceFactory().get(getOptions().getMappings());
var tinyMappingsReader = new TinyMappingsReader(mappingsService.getMemoryMappingTree(), mappingsService.getFrom(), mappingsService.getTo()).read();
mercury.getProcessors().add(MercuryRemapper.create(tinyMappingsReader));
for (File file : classpath.getFiles()) {
for (File file : getOptions().getClasspath().getFiles()) {
if (file.exists()) {
mercury.getClassPath().add(file.toPath());
}

View File

@@ -0,0 +1,165 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2024 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.util.gradle;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import org.gradle.api.file.FileCollection;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.MapProperty;
import org.gradle.api.provider.Property;
@SuppressWarnings({"rawtypes", "unchecked"})
public class GradleTypeAdapter implements TypeAdapterFactory {
public static final Gson GSON = new Gson().newBuilder()
.registerTypeAdapterFactory(new GradleTypeAdapter())
.create();
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
final Class<? super T> rawClass = type.getRawType();
if (FileCollection.class.isAssignableFrom(rawClass)) {
return new FileCollectionTypeAdapter();
} else if (RegularFileProperty.class.isAssignableFrom(rawClass)) {
return new RegularFilePropertyTypeAdapter();
} else if (ListProperty.class.isAssignableFrom(rawClass)) {
return new ListPropertyTypeAdapter(gson);
} else if (MapProperty.class.isAssignableFrom(rawClass)) {
return new MapPropertyTypeAdapter(gson);
} else if (Property.class.isAssignableFrom(rawClass)) {
return new PropertyTypeAdapter(gson);
}
return null;
}
private static final class PropertyTypeAdapter<T extends Property<?>> extends WriteOnlyTypeAdapter<T> {
private final Gson gson;
private PropertyTypeAdapter(Gson gson) {
this.gson = gson;
}
@Override
public void write(JsonWriter out, T property) throws IOException {
final Object o = property.get();
final TypeAdapter adapter = gson.getAdapter(o.getClass());
adapter.write(out, o);
}
}
private static final class FileCollectionTypeAdapter<T extends FileCollection> extends WriteOnlyTypeAdapter<T> {
@Override
public void write(JsonWriter out, T fileCollection) throws IOException {
out.beginArray();
final List<String> files = fileCollection.getFiles().stream()
.map(File::getAbsolutePath)
.sorted()
.toList();
for (String file : files) {
out.value(file);
}
out.endArray();
}
}
private static final class RegularFilePropertyTypeAdapter<T extends RegularFileProperty> extends WriteOnlyTypeAdapter<T> {
@Override
public void write(JsonWriter out, T property) throws IOException {
final File file = property.get().getAsFile();
out.value(file.getAbsolutePath());
}
}
private static final class ListPropertyTypeAdapter<T extends ListProperty<?>> extends WriteOnlyTypeAdapter<T> {
private final Gson gson;
private ListPropertyTypeAdapter(Gson gson) {
this.gson = gson;
}
@Override
public void write(JsonWriter out, T property) throws IOException {
List<?> objects = property.get();
out.beginArray();
for (Object o : objects) {
final TypeAdapter adapter = gson.getAdapter(o.getClass());
adapter.write(out, o);
}
out.endArray();
}
}
private static final class MapPropertyTypeAdapter<T extends MapProperty<?, ?>> extends WriteOnlyTypeAdapter<T> {
private final Gson gson;
private MapPropertyTypeAdapter(Gson gson) {
this.gson = gson;
}
@Override
public void write(JsonWriter out, T property) throws IOException {
out.beginObject();
for (Map.Entry<?, ?> entry : property.get().entrySet()) {
Object key = entry.getKey();
Object value = entry.getValue();
if (!(key instanceof String)) {
throw new UnsupportedOperationException("Map keys must be strings");
}
out.name(entry.getKey().toString());
final TypeAdapter adapter = gson.getAdapter(value.getClass());
adapter.write(out, value);
}
out.endObject();
}
}
private abstract static class WriteOnlyTypeAdapter<T> extends TypeAdapter<T> {
@Override
public final T read(JsonReader in) {
throw new UnsupportedOperationException("This type adapter is write-only");
}
}
}

View File

@@ -0,0 +1,102 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2024 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.util.newService;
import java.io.Closeable;
import java.io.IOException;
import org.gradle.api.Project;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.Nested;
// Note: This file mostly acts as documentation for the new service system.
// Services are classes that wrap expensive to create objects, such as mappings or tiny remapper.
// Services can be reused multiple times within a given scope, such as configuration or a single task action.
// Services need to have serializable inputs, used as a cache key and serialised to be passed between Gradle contexts. E.g task inputs, or work action params.
public final class ExampleService extends Service<ExampleService.Options> implements Closeable {
public static ServiceType<Options, ExampleService> TYPE = new ServiceType<>(Options.class, ExampleService.class);
// Options use Gradle's Property's thus can be used in task inputs.
public interface Options extends Service.Options {
@Nested
Property<AnotherService.Options> getNested();
}
// Options can be created using data from the Project
static Provider<Options> createOptions(Project project) {
return TYPE.create(project, o -> {
o.getNested().set(AnotherService.createOptions(project, "example"));
});
}
// An example of how a service could be used, this could be within a task action.
// ServiceFactory would be similar to the existing ScopedSharedServiceManager
// Thus if a service with the same options has previously been created it will be reused.
static void howToUse(Options options, ServiceFactory factory) {
ExampleService exampleService = factory.get(options);
exampleService.doSomething();
}
public ExampleService(Options options, ServiceFactory serviceFactory) {
super(options, serviceFactory);
}
public void doSomething() {
// The service factory used to the creation the current service can be used to get or create other services based on the current service's options.
AnotherService another = getServiceFactory().get(getOptions().getNested());
System.out.println("ExampleService: " + another.getExample());
}
@Override
public void close() throws IOException {
// Anything that needs to be cleaned up when the service is no longer needed.
}
public static final class AnotherService extends Service<AnotherService.Options> {
public static ServiceType<Options, AnotherService> TYPE = new ServiceType<>(Options.class, AnotherService.class);
public interface Options extends Service.Options {
@Input
Property<String> getExample();
}
static Provider<AnotherService.Options> createOptions(Project project, String example) {
return TYPE.create(project, o -> {
o.getExample().set(example);
});
}
public AnotherService(Options options, ServiceFactory serviceFactory) {
super(options, serviceFactory);
}
// Services can expose any methods they wish, either to return data or do a job.
public String getExample() {
return getOptions().getExample().get();
}
}
}

View File

@@ -0,0 +1,120 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2024 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.util.newService;
import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
import net.fabricmc.loom.util.gradle.GradleTypeAdapter;
/**
* An implementation of {@link ServiceFactory} that creates services scoped to the factory instance.
* When the factory is closed, all services created by it are closed and discarded.
*/
public final class ScopedServiceFactory implements ServiceFactory, Closeable {
private final Map<Service.Options, Service<?>> servicesIdentityMap = new IdentityHashMap<>();
private final Map<String, Service<?>> servicesJsonMap = new HashMap<>();
@Override
public <O extends Service.Options, S extends Service<O>> S get(O options) {
// First check if the service is already created, using the identity map saving the need to serialize the options
//noinspection unchecked
S service = (S) servicesIdentityMap.get(options);
if (service != null) {
return service;
}
// TODO skip serialization if we know there is no service with the same type
// If the service is not already created, serialize the options and check the json map as it may be an equivalent service
String key = getOptionsCacheKey(options);
//noinspection unchecked
service = (S) servicesJsonMap.get(key);
if (service != null) {
return service;
}
service = createService(options, this);
servicesIdentityMap.put(options, service);
servicesJsonMap.put(key, service);
return service;
}
private static <O extends Service.Options, S extends Service<O>> S createService(O options, ServiceFactory serviceFactory) {
// We need to create the service from the provided options
final Class<? extends S> serviceClass;
// Find the service class
try {
//noinspection unchecked
serviceClass = (Class<? extends S>) Class.forName(options.getServiceClass().get());
} catch (ClassNotFoundException e) {
throw new RuntimeException("Failed to find service class: " + options.getServiceClass().get(), e);
}
try {
// Check there is only 1 constructor
if (serviceClass.getDeclaredConstructors().length != 1) {
throw new RuntimeException("Service class must have exactly 1 constructor");
}
// Check the constructor takes the correct types, the options class and a ScopedServiceFactory
Class<?>[] parameterTypes = serviceClass.getDeclaredConstructors()[0].getParameterTypes();
if (parameterTypes.length != 2 || !parameterTypes[0].isAssignableFrom(options.getClass()) || !parameterTypes[1].isAssignableFrom(ServiceFactory.class)) {
throw new RuntimeException("Service class" + serviceClass.getName() + " constructor must take the options class and a ScopedServiceFactory");
}
//noinspection unchecked
return (S) serviceClass.getDeclaredConstructors()[0].newInstance(options, serviceFactory);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException("Failed to create service instance", e);
}
}
private String getOptionsCacheKey(Service.Options options) {
return GradleTypeAdapter.GSON.toJson(options);
}
@Override
public void close() throws IOException {
for (Service<?> service : servicesIdentityMap.values()) {
if (service instanceof Closeable closeable) {
closeable.close();
}
}
servicesIdentityMap.clear();
servicesJsonMap.clear();
}
}

View File

@@ -0,0 +1,71 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2024 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.util.newService;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.jetbrains.annotations.ApiStatus;
/**
* A service is used to manage a set of data or a task that may be reused multiple times.
*
* @param <O> The options type.
*/
public abstract class Service<O extends Service.Options> {
private final O options;
private final ServiceFactory serviceFactory;
public Service(O options, ServiceFactory serviceFactory) {
this.options = options;
this.serviceFactory = serviceFactory;
}
/**
* Gets the options for this service.
*
* @return The options.
*/
protected final O getOptions() {
return options;
}
/**
* Return the factory that created this service, this can be used to get nested services.
*
* @return The {@link ServiceFactory} instance.
*/
protected ServiceFactory getServiceFactory() {
return serviceFactory;
}
/**
* The base type of options class for a service.
*/
public interface Options {
@Input
@ApiStatus.Internal
Property<String> getServiceClass();
}
}

View File

@@ -0,0 +1,54 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2024 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.util.newService;
import org.gradle.api.provider.Property;
/**
* A factory for creating {@link Service} instances.
*/
public interface ServiceFactory {
/**
* Gets or creates a service instance with the given options.
*
* @param options The options to use.
* @param <O> The options type.
* @param <S> The service type.
* @return The service instance.
*/
default <O extends Service.Options, S extends Service<O>> S get(Property<O> options) {
return get(options.get());
}
/**
* Gets or creates a service instance with the given options.
*
* @param options The options to use.
* @param <O> The options type.
* @param <S> The service type.
* @return The service instance.
*/
<O extends Service.Options, S extends Service<O>> S get(O options);
}

View File

@@ -0,0 +1,52 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2024 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.util.newService;
import org.gradle.api.Action;
import org.gradle.api.Project;
import org.gradle.api.provider.Provider;
/**
* A record to hold the options and service class for a service.
* @param optionsClass The options class for the service.
* @param serviceClass The service class.
*/
public record ServiceType<O extends Service.Options, S extends Service<O>>(Class<O> optionsClass, Class<S> serviceClass) {
/**
* Create an instance of the options class for the given service class.
* @param project The {@link Project} to create the options for.
* @param action An action to configure the options.
* @return The created options instance.
*/
public Provider<O> create(Project project, Action<O> action) {
return project.provider(() -> {
O options = project.getObjects().newInstance(optionsClass);
options.getServiceClass().set(serviceClass.getName());
options.getServiceClass().finalizeValue();
action.execute(options);
return options;
});
}
}

View File

@@ -60,6 +60,7 @@ class ConfigurationCacheTest extends Specification implements GradleProjectTestT
"configureClientLaunch" | _
"jar" | _
"check" | _
"remapSourcesJar" | _
}
// Test GradleUtils.configurationInputFile invalidates the cache when the file changes

View File

@@ -0,0 +1,107 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2024 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.test.unit
import org.gradle.api.file.FileCollection
import org.gradle.api.file.RegularFile
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import spock.lang.IgnoreIf
import spock.lang.Specification
import net.fabricmc.loom.util.gradle.GradleTypeAdapter
class GradleTypeAdapterTest extends Specification {
def "Property"() {
given:
def property = Mock(Property)
when:
def json = GradleTypeAdapter.GSON.toJson(property)
then:
1 * property.get() >> "value"
json == "\"value\""
}
@IgnoreIf({ os.windows })
def "FileCollection"() {
given:
def file1 = new File("file1")
def file2 = new File("file2")
def fileCollection = Mock(FileCollection)
when:
def json = GradleTypeAdapter.GSON.toJson(fileCollection)
then:
1 * fileCollection.getFiles() >> [file1, file2].shuffled()
json == "[\"${file1.getAbsolutePath()}\",\"${file2.getAbsolutePath()}\"]"
}
@IgnoreIf({ os.windows })
def "RegularFileProperty"() {
given:
def file = new File("file")
def regularFileProperty = Mock(RegularFileProperty)
def regularFile = Mock(RegularFile)
when:
def json = GradleTypeAdapter.GSON.toJson(regularFileProperty)
then:
1 * regularFileProperty.get() >> regularFile
1 * regularFile.getAsFile() >> file
json == "\"${file.getAbsolutePath()}\""
}
def "ListProperty"() {
given:
def listProperty = Mock(ListProperty)
def list = ["value1", "value2"]
when:
def json = GradleTypeAdapter.GSON.toJson(listProperty)
then:
1 * listProperty.get() >> list
json == "[\"value1\",\"value2\"]"
}
def "MapProperty"() {
given:
def mapProperty = Mock(MapProperty)
def map = ["key1": "value1", "key2": "value2"]
when:
def json = GradleTypeAdapter.GSON.toJson(mapProperty)
then:
1 * mapProperty.get() >> map
json == "{\"key1\":\"value1\",\"key2\":\"value2\"}"
}
}

View File

@@ -0,0 +1,59 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2024 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.test.unit.service
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import net.fabricmc.loom.task.service.NewMappingsService
import net.fabricmc.loom.test.util.GradleTestUtil
class MappingsServiceTest extends ServiceTestBase {
def "get mapping tree"() {
given:
NewMappingsService service = factory.get(new TestOptions(
mappingsFile: GradleTestUtil.mockRegularFileProperty(new File("src/test/resources/mappings/PosInChunk.mappings")),
from: GradleTestUtil.mockProperty("intermediary"),
to: GradleTestUtil.mockProperty("named"),
))
when:
def mappingTree = service.memoryMappingTree
then:
mappingTree.getClasses().size() == 2
service.from == "intermediary"
service.to == "named"
}
static class TestOptions implements NewMappingsService.Options {
RegularFileProperty mappingsFile
Property<String> from
Property<String> to
Property<Boolean> remapLocals = GradleTestUtil.mockProperty(false)
Property<String> serviceClass = serviceClassProperty(NewMappingsService.TYPE)
}
}

View File

@@ -0,0 +1,145 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2024 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.test.unit.service
import groovy.transform.InheritConstructors
import groovy.transform.TupleConstructor
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import spock.lang.Specification
import net.fabricmc.loom.test.util.GradleTestUtil
import net.fabricmc.loom.util.newService.ScopedServiceFactory
import net.fabricmc.loom.util.newService.Service
import net.fabricmc.loom.util.newService.ServiceType
class ScopedServiceFactoryTest extends Specification {
def "create service"() {
given:
def options = new TestServiceOptions(GradleTestUtil.mockProperty("hello"))
def factory = new ScopedServiceFactory()
when:
TestService service = factory.get(options)
then:
service.getExample() == "hello"
cleanup:
factory.close()
}
def "reuse service"() {
given:
def options = new TestServiceOptions(GradleTestUtil.mockProperty("hello"))
def factory = new ScopedServiceFactory()
when:
TestService service = factory.get(options)
TestService service2 = factory.get(options)
then:
service === service2
cleanup:
factory.close()
}
def "reuse service different options instance"() {
given:
def options = new TestServiceOptions(GradleTestUtil.mockProperty("hello"))
def options2 = new TestServiceOptions(GradleTestUtil.mockProperty("hello"))
def factory = new ScopedServiceFactory()
when:
TestService service = factory.get(options)
TestService service2 = factory.get(options2)
then:
service === service2
cleanup:
factory.close()
}
def "Separate instances"() {
given:
def options = new TestServiceOptions(GradleTestUtil.mockProperty("hello"))
def options2 = new TestServiceOptions(GradleTestUtil.mockProperty("world"))
def factory = new ScopedServiceFactory()
when:
TestService service = factory.get(options)
TestService service2 = factory.get(options2)
then:
service !== service2
service.example == "hello"
service2.example == "world"
cleanup:
factory.close()
}
def "close service"() {
given:
def options = new TestServiceOptions(GradleTestUtil.mockProperty("hello"))
def factory = new ScopedServiceFactory()
when:
TestService service = factory.get(options)
factory.close()
then:
service.closed
}
@InheritConstructors
static class TestService extends Service<Options> implements Closeable {
static ServiceType<TestService.Options, TestService> TYPE = new ServiceType(TestService.Options.class, TestService.class)
interface Options extends Service.Options {
@Input
Property<String> getExample();
}
boolean closed = false
String getExample() {
return options.example.get()
}
@Override
void close() throws Exception {
closed = true
}
}
@TupleConstructor
static class TestServiceOptions implements TestService.Options {
Property<String> example
Property<String> serviceClass = ServiceTestBase.serviceClassProperty(TestService.TYPE)
}
}

View File

@@ -0,0 +1,50 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2024 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.test.unit.service
import org.gradle.api.provider.Property
import spock.lang.Specification
import net.fabricmc.loom.test.util.GradleTestUtil
import net.fabricmc.loom.util.newService.ScopedServiceFactory
import net.fabricmc.loom.util.newService.Service
import net.fabricmc.loom.util.newService.ServiceType
abstract class ServiceTestBase extends Specification {
ScopedServiceFactory factory
def setup() {
factory = new ScopedServiceFactory()
}
def cleanup() {
factory.close()
factory = null
}
static Property<String> serviceClassProperty(ServiceType<? extends Service.Options, ? extends Service> type) {
return GradleTestUtil.mockProperty(type.serviceClass().name)
}
}

View File

@@ -0,0 +1,95 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2024 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.test.unit.service
import java.nio.file.Files
import java.nio.file.Path
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.provider.Property
import org.intellij.lang.annotations.Language
import net.fabricmc.loom.task.service.NewMappingsService
import net.fabricmc.loom.task.service.SourceRemapperService
import net.fabricmc.loom.test.util.GradleTestUtil
import net.fabricmc.loom.util.DeletingFileVisitor
class SourceRemapperServiceTest extends ServiceTestBase {
def "remap sources"() {
given:
Path tempDirectory = Files.createTempDirectory("test")
Path sourceDirectory = tempDirectory.resolve("source")
Path destDirectory = tempDirectory.resolve("dst")
Path mappings = tempDirectory.resolve("mappings.tiny")
Files.createDirectories(sourceDirectory)
Files.createDirectories(destDirectory)
Files.writeString(sourceDirectory.resolve("Source.java"), SOURCE)
Files.writeString(mappings, MAPPINGS)
SourceRemapperService service = factory.get(new TestOptions(
mappings: GradleTestUtil.mockProperty(
new MappingsServiceTest.TestOptions(
mappingsFile: GradleTestUtil.mockRegularFileProperty(mappings.toFile()),
from: GradleTestUtil.mockProperty("named"),
to: GradleTestUtil.mockProperty("intermediary"),
)
),
))
when:
service.remapSourcesJar(sourceDirectory, destDirectory)
then:
// This isn't actually remapping, as we dont have the classpath setup at all. But that's not what we're testing here.
!Files.readString(destDirectory.resolve("Source.java")).isEmpty()
cleanup:
Files.walkFileTree(tempDirectory, new DeletingFileVisitor())
}
@Language("java")
static String SOURCE = """
class Source {
public void test() {
System.out.println("Hello");
}
}
""".trim()
// Tiny v2 mappings to rename println
static String MAPPINGS = """
tiny 2 0 intermediary named
c Source Source
m ()V println test
""".trim()
static class TestOptions implements SourceRemapperService.Options {
Property<NewMappingsService.Options> mappings
Property<Integer> javaCompileRelease = GradleTestUtil.mockProperty(17)
ConfigurableFileCollection classpath = GradleTestUtil.mockConfigurableFileCollection()
Property<String> serviceClass = serviceClassProperty(SourceRemapperService.TYPE)
}
}

View File

@@ -26,6 +26,7 @@ package net.fabricmc.loom.test.util
import org.gradle.api.Project
import org.gradle.api.artifacts.dsl.RepositoryHandler
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFile
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.file.SourceDirectorySet
@@ -132,6 +133,12 @@ class GradleTestUtil {
return mock
}
static ConfigurableFileCollection mockConfigurableFileCollection(File... files) {
def mock = mock(ConfigurableFileCollection.class)
when(mock.getFiles()).thenReturn(Set.of(files))
return mock
}
static RepositoryHandler mockRepositoryHandler() {
def mock = mock(RepositoryHandler.class)
return mock