mirror of
https://github.com/architectury/architectury-loom.git
synced 2026-03-28 04:07:01 -05:00
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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
71
src/main/java/net/fabricmc/loom/util/newService/Service.java
Normal file
71
src/main/java/net/fabricmc/loom/util/newService/Service.java
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -60,6 +60,7 @@ class ConfigurationCacheTest extends Specification implements GradleProjectTestT
|
||||
"configureClientLaunch" | _
|
||||
"jar" | _
|
||||
"check" | _
|
||||
"remapSourcesJar" | _
|
||||
}
|
||||
|
||||
// Test GradleUtils.configurationInputFile invalidates the cache when the file changes
|
||||
|
||||
@@ -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\"}"
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user