diff --git a/common/src/main/java/org/implab/gradle/common/core/lang/FilePaths.java b/common/src/main/java/org/implab/gradle/common/core/lang/FilePaths.java new file mode 100644 --- /dev/null +++ b/common/src/main/java/org/implab/gradle/common/core/lang/FilePaths.java @@ -0,0 +1,75 @@ +package org.implab.gradle.common.core.lang; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collector; +import java.util.stream.Stream; + +public final class FilePaths { + private static PathCollector defaultPathCollector = new PathCollector(); + + private FilePaths() { + } + + public static String cat(String fist, String... extra) { + return catPath(fist, extra).toString(); + } + + public static Path catPath(String first, String ...extra) { + return Stream.concat(Stream.of(first), Stream.of(extra)) + .collect(asPath()); + } + + public static Collector, Path> asPath() { + return defaultPathCollector; + } + + static class PathCollector implements Collector, Path> { + + private final Function sanitizer; + + public PathCollector() { + sanitizer = Strings::sanitizeFileName; + } + + @Override + public BiConsumer, String> accumulator() { + return (components, item) -> components.add(sanitizer.apply(item)); + } + + @Override + public Set characteristics() { + return Set.of(); + } + + @Override + public BinaryOperator> combiner() { + return (first, extra) -> { + first.addAll(extra); + return first; + }; + } + + @Override + public Function, Path> finisher() { + return components -> Paths.get( + components.get(0), + components.subList(1, components.size()) + .toArray(String[]::new)); + + } + + @Override + public Supplier> supplier() { + return () -> new LinkedList<>(); + } + + } +} diff --git a/common/src/main/java/org/implab/gradle/common/core/lang/Strings.java b/common/src/main/java/org/implab/gradle/common/core/lang/Strings.java --- a/common/src/main/java/org/implab/gradle/common/core/lang/Strings.java +++ b/common/src/main/java/org/implab/gradle/common/core/lang/Strings.java @@ -1,5 +1,7 @@ package org.implab.gradle.common.core.lang; +import java.util.function.Consumer; +import java.util.function.Function; import java.util.regex.Pattern; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -8,10 +10,26 @@ import org.gradle.api.provider.Provider; @NonNullByDefault public class Strings { + private static final boolean[] ALLOWED_FILE_NAME_CHAR = new boolean[128]; + + private static final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray(); + private static final Pattern firstLetter = Pattern.compile("^\\w"); private static final Pattern INVALID_NAME_CHAR = Pattern.compile("[^A-Za-z0-9_.-]"); + static { + for (char c = '0'; c <= '9'; c++) + ALLOWED_FILE_NAME_CHAR[c] = true; + for (char c = 'A'; c <= 'Z'; c++) + ALLOWED_FILE_NAME_CHAR[c] = true; + for (char c = 'a'; c <= 'z'; c++) + ALLOWED_FILE_NAME_CHAR[c] = true; + ALLOWED_FILE_NAME_CHAR['.'] = true; + ALLOWED_FILE_NAME_CHAR['_'] = true; + ALLOWED_FILE_NAME_CHAR['-'] = true; + } + public static String capitalize(String string) { return string == null ? null : string.length() == 0 ? string @@ -55,10 +73,26 @@ public class Strings { return value; } + public static String requireNonEmpty(String value) { + argumentNotNullOrEmpty(value, "value"); + return value; + } + + public static String sanitizeName(String value) { return INVALID_NAME_CHAR.matcher(value).replaceAll("_"); } + public static String sanitizeFileName(String value) { + int length = value.length(); + for (int i = 0; i < length; i++) { + char c = value.charAt(i); + if (c >= ALLOWED_FILE_NAME_CHAR.length || !ALLOWED_FILE_NAME_CHAR[c]) + return sanitizeFileName(value, i); + } + return value; + } + public static String asString(Object value) { if (value == null) return null; @@ -67,4 +101,49 @@ public class Strings { else return value.toString(); } + + private static String sanitizeFileName(String value, int invalidIndex) { + int length = value.length(); + StringBuilder out = new StringBuilder(length + 16); + out.append(value, 0, invalidIndex); + + for (int i = invalidIndex; i < length; i++) { + char c = value.charAt(i); + if (c < ALLOWED_FILE_NAME_CHAR.length && ALLOWED_FILE_NAME_CHAR[c]) { + out.append(c); + } else if (Character.isHighSurrogate(c) && i + 1 < length && Character.isLowSurrogate(value.charAt(i + 1))) { + appendUrlEncodedUtf8(out, Character.toCodePoint(c, value.charAt(++i))); + } else if (Character.isSurrogate(c)) { + appendUrlEncodedUtf8(out, 0xFFFD); + } else { + appendUrlEncodedUtf8(out, c); + } + } + + return out.toString(); + } + + private static void appendUrlEncodedUtf8(StringBuilder out, int codePoint) { + if (codePoint <= 0x7F) { + appendUrlEncodedByte(out, codePoint); + } else if (codePoint <= 0x7FF) { + appendUrlEncodedByte(out, 0xC0 | (codePoint >>> 6)); + appendUrlEncodedByte(out, 0x80 | (codePoint & 0x3F)); + } else if (codePoint <= 0xFFFF) { + appendUrlEncodedByte(out, 0xE0 | (codePoint >>> 12)); + appendUrlEncodedByte(out, 0x80 | ((codePoint >>> 6) & 0x3F)); + appendUrlEncodedByte(out, 0x80 | (codePoint & 0x3F)); + } else { + appendUrlEncodedByte(out, 0xF0 | (codePoint >>> 18)); + appendUrlEncodedByte(out, 0x80 | ((codePoint >>> 12) & 0x3F)); + appendUrlEncodedByte(out, 0x80 | ((codePoint >>> 6) & 0x3F)); + appendUrlEncodedByte(out, 0x80 | (codePoint & 0x3F)); + } + } + + private static void appendUrlEncodedByte(StringBuilder out, int value) { + out.append('%'); + out.append(HEX_DIGITS[(value >>> 4) & 0x0F]); + out.append(HEX_DIGITS[value & 0x0F]); + } } diff --git a/variants/src/main/java/org/implab/gradle/variants/VariantArtifactsPlugin.java b/variants/src/main/java/org/implab/gradle/variants/VariantArtifactsPlugin.java --- a/variants/src/main/java/org/implab/gradle/variants/VariantArtifactsPlugin.java +++ b/variants/src/main/java/org/implab/gradle/variants/VariantArtifactsPlugin.java @@ -10,6 +10,7 @@ import org.implab.gradle.variants.artifa import org.implab.gradle.variants.artifacts.VariantArtifactsExtension; import org.implab.gradle.variants.artifacts.VariantArtifactsSpec; import org.implab.gradle.variants.artifacts.internal.ArtifactAssemblyBridge; +import org.implab.gradle.variants.artifacts.internal.DefaultVariantArtifactSpec; import org.implab.gradle.variants.artifacts.internal.OutgoingRegistry; import org.implab.gradle.variants.core.Variant; import org.implab.gradle.variants.core.VariantsExtension; @@ -32,6 +33,7 @@ public abstract class VariantArtifactsPl var outgoing = new OutgoingRegistry(configurations, objects, providers); var assemblies = new ArtifactAssemblyRegistry(objects, tasks); + // wire artifact assemblies to configuration variants var assembliesBridge = new ArtifactAssemblyBridge(assemblies); var deferred = new Deferred(); @@ -46,8 +48,9 @@ public abstract class VariantArtifactsPl @Override public void variant(String variantName, Action action) { - deferred.whenResolved(context -> registry.configureVariant( - objects.named(Variant.class, variantName), action)); + deferred.whenResolved(context -> { + new DefaultVariantArtifactSpec(); + }); } @Override diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/ArtifactAssembly.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/ArtifactAssembly.java --- a/variants/src/main/java/org/implab/gradle/variants/artifacts/ArtifactAssembly.java +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/ArtifactAssembly.java @@ -1,7 +1,6 @@ package org.implab.gradle.variants.artifacts; import org.gradle.api.Task; -import org.gradle.api.file.FileCollection; import org.gradle.api.file.FileSystemLocation; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.TaskProvider; @@ -35,9 +34,4 @@ public interface ArtifactAssembly { */ TaskProvider getAssemblyTask(); - /** - * File collection, contains {@link #getArtifact()} and build dependency on - * {@link #getAssemblyTask()}. This is a conventional property. - */ - FileCollection getFileCollection(); } diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/ArtifactAssemblyRegistry.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/ArtifactAssemblyRegistry.java --- a/variants/src/main/java/org/implab/gradle/variants/artifacts/ArtifactAssemblyRegistry.java +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/ArtifactAssemblyRegistry.java @@ -9,43 +9,18 @@ import org.eclipse.jdt.annotation.NonNul import org.gradle.api.Action; import org.gradle.api.InvalidUserDataException; import org.gradle.api.Task; -import org.gradle.api.file.Directory; -import org.gradle.api.file.FileCollection; import org.gradle.api.file.FileSystemLocation; -import org.gradle.api.model.ObjectFactory; import org.gradle.api.provider.Provider; -import org.gradle.api.tasks.Copy; -import org.gradle.api.tasks.TaskContainer; import org.gradle.api.tasks.TaskProvider; -import org.gradle.language.base.plugins.LifecycleBasePlugin; import org.implab.gradle.common.core.lang.Deferred; import org.implab.gradle.internal.ReplayableQueue; @NonNullByDefault public class ArtifactAssemblyRegistry implements ArtifactAssemblies { - private final ObjectFactory objects; - private final TaskContainer tasks; private final Map> assembliesBySlots = new LinkedHashMap<>(); private final ReplayableQueue assemblies = new ReplayableQueue<>(); - public ArtifactAssemblyRegistry(ObjectFactory objects, TaskContainer tasks) { - this.objects = objects; - this.tasks = tasks; - } - - public ArtifactAssembly register( - ArtifactSlot slot, - String taskName, - Provider outputDirectory, - FileCollection sources) { - - var task = tasks.register(taskName, Copy.class, copy -> { - copy.setGroup(LifecycleBasePlugin.BUILD_GROUP); - copy.into(outputDirectory); - copy.from(sources); - }); - - return register(slot, task, t -> outputDirectory); + public ArtifactAssemblyRegistry() { } public ArtifactAssembly register( @@ -59,11 +34,7 @@ public class ArtifactAssemblyRegistry im } var outputArtifact = task.flatMap(mapOutputArtifact::apply); - var output = objects.fileCollection() - .from(outputArtifact) - .builtBy(task); - - var assembly = new Assembly(outputArtifact, task, output); + var assembly = new Assembly(outputArtifact, task); deferred.resolve(assembly); assemblies.add(assembly); return assembly; @@ -96,13 +67,10 @@ public class ArtifactAssemblyRegistry im private final Provider artifact; private final TaskProvider task; - private final FileCollection fileCollection; - Assembly(Provider artifact, TaskProvider task, - FileCollection fileCollection) { + Assembly(Provider artifact, TaskProvider task) { this.artifact = artifact; this.task = task; - this.fileCollection = fileCollection; } @Override @@ -115,10 +83,5 @@ public class ArtifactAssemblyRegistry im return task; } - @Override - public FileCollection getFileCollection() { - return fileCollection; - } - } } diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/ArtifactConfigurationHandler.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/ArtifactConfigurationHandler.java new file mode 100644 --- /dev/null +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/ArtifactConfigurationHandler.java @@ -0,0 +1,100 @@ +package org.implab.gradle.variants.artifacts.internal; + +import java.util.HashMap; +import java.util.Map; + +import org.gradle.api.Action; +import org.gradle.api.file.Directory; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.model.ObjectFactory; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.Copy; +import org.gradle.api.tasks.TaskContainer; +import org.gradle.language.base.plugins.LifecycleBasePlugin; +import org.implab.gradle.common.core.lang.FilePaths; +import org.implab.gradle.common.core.lang.Strings; +import org.implab.gradle.variants.artifacts.ArtifactAssemblyRegistry; +import org.implab.gradle.variants.artifacts.ArtifactSlot; +import org.implab.gradle.variants.artifacts.OutgoingVariant; +import org.implab.gradle.variants.artifacts.VariantArtifactsSpec; +import org.implab.gradle.variants.sources.CompileUnitsView; +import org.implab.gradle.variants.sources.RoleProjectionsView; +import org.implab.gradle.variants.sources.SourceSetMaterializer; + +public class ArtifactConfigurationHandler { + private final ArtifactAssemblyRegistry registry; + + private final CompileUnitsView compileUnitsView; + + private final RoleProjectionsView roleProjectionsView; + + private final SourceSetMaterializer sourceSetMaterializer; + + private final Map visitors = new HashMap<>(); + + private final ObjectFactory objectFactory; + + private final DirectoryProperty assembliesDirectory; + + private final TaskContainer tasks; + + public ArtifactConfigurationHandler( + ArtifactAssemblyRegistry registry, + CompileUnitsView compileUnitsView, + RoleProjectionsView roleProjectionsView, + SourceSetMaterializer sourceSetMaterializer, + ObjectFactory objectFactory, + TaskContainer tasks) { + this.registry = registry; + this.compileUnitsView = compileUnitsView; + this.roleProjectionsView = roleProjectionsView; + this.sourceSetMaterializer = sourceSetMaterializer; + this.objectFactory = objectFactory; + this.assembliesDirectory = objectFactory.directoryProperty(); + this.tasks = tasks; + } + + public SlotContributionVisitor slotAssembler(ArtifactSlot artifactSlot) { + return visitors.computeIfAbsent(artifactSlot, this::newVisitor); + } + + public void configureVariant(OutgoingVariant outgoingVariant, Action action) { + var spec = new DefaultVariantArtifactSpec(outgoingVariant, objectFactory, this); + + action.execute(spec); + } + + private SlotContributionVisitor newVisitor(ArtifactSlot artifactSlot) { + var fileCollection = objectFactory.fileCollection(); + var outputDirectory = outputDirectory(artifactSlot); + + var task = tasks.register(assembleTaskName(artifactSlot), Copy.class, copy -> { + copy.setGroup(LifecycleBasePlugin.BUILD_GROUP); + copy.into(outputDirectory); + copy.from(fileCollection); + }); + + // регистрируется задача и арефакт сборки слота + registry.register(artifactSlot, task, t -> outputDirectory); + + return new SlotInputsAssembler( + artifactSlot, + fileCollection, + compileUnitsView, + roleProjectionsView, + sourceSetMaterializer); + } + + private String assembleTaskName(ArtifactSlot artifactSlot) { + return "assemble" + + Strings.capitalize(artifactSlot.variant().getName()) + + Strings.capitalize(artifactSlot.slot().getName()); + } + + private Provider outputDirectory(ArtifactSlot artifactSlot) { + return assembliesDirectory.dir( + FilePaths.cat( + artifactSlot.variant().getName(), + artifactSlot.slot().getName())); + } +} diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/BoundArtifactAssemblySpec.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/DefaultArtifactAssemblySpec.java rename from variants/src/main/java/org/implab/gradle/variants/artifacts/internal/BoundArtifactAssemblySpec.java rename to variants/src/main/java/org/implab/gradle/variants/artifacts/internal/DefaultArtifactAssemblySpec.java --- a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/BoundArtifactAssemblySpec.java +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/DefaultArtifactAssemblySpec.java @@ -1,8 +1,6 @@ package org.implab.gradle.variants.artifacts.internal; import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; import java.util.Set; import java.util.function.Consumer; import java.util.stream.Stream; @@ -21,43 +19,40 @@ import org.implab.gradle.variants.artifa * вызывает метод {@link #process(Consumer)} для обработки результатов. * */ -final class BoundArtifactAssemblySpec implements ArtifactAssemblySpec { - private final List contributions = new LinkedList<>(); +final class DefaultArtifactAssemblySpec implements ArtifactAssemblySpec { + private final Consumer consumer; private final ObjectFactory objectFactory; - BoundArtifactAssemblySpec(ObjectFactory objectFactory) { + DefaultArtifactAssemblySpec(ObjectFactory objectFactory, Consumer consumer) { + this.consumer = consumer; this.objectFactory = objectFactory; } @Override public void from(Object artifact) { - contributions.add(new DirectContribution(artifact)); + consumer.accept(new DirectContribution(artifact)); } @Override public void fromVariant(Action action) { - contributions.add(new VariantOutputsContribution(outputs(action))); + consumer.accept(new VariantOutputsContribution(outputs(action))); } @Override public void fromRole(String roleName, Action action) { - contributions.add(new RoleOutputsContribution( + consumer.accept(new RoleOutputsContribution( objectFactory.named(Role.class, roleName), outputs(action))); } @Override public void fromLayer(String layerName, Action action) { - contributions.add(new LayerOutputsContribution( + consumer.accept(new LayerOutputsContribution( objectFactory.named(Layer.class, layerName), outputs(action))); } - void process(Consumer consumer) { - contributions.forEach(consumer); - } - private static Set outputs(Action action) { var spec = new OutputsSetSpec(); action.execute(spec); diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/DefaultVariantArtifactSpec.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/DefaultVariantArtifactSpec.java new file mode 100644 --- /dev/null +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/DefaultVariantArtifactSpec.java @@ -0,0 +1,51 @@ +package org.implab.gradle.variants.artifacts.internal; + +import org.gradle.api.Action; +import org.gradle.api.model.ObjectFactory; +import org.implab.gradle.variants.artifacts.ArtifactAssemblySpec; +import org.implab.gradle.variants.artifacts.ArtifactSlot; +import org.implab.gradle.variants.artifacts.OutgoingVariant; +import org.implab.gradle.variants.artifacts.Slot; +import org.implab.gradle.variants.artifacts.VariantArtifactsSpec; + +class DefaultVariantArtifactSpec implements VariantArtifactsSpec { + + private final ObjectFactory objectFactory; + private final ArtifactConfigurationHandler assemblyBuilder; + + private final OutgoingVariant outgoingVariant; + + DefaultVariantArtifactSpec( + OutgoingVariant outgoingVariant, + ObjectFactory objectFactory, + ArtifactConfigurationHandler assemblyBuilder) { + this.objectFactory = objectFactory; + this.assemblyBuilder = assemblyBuilder; + this.outgoingVariant = outgoingVariant; + } + + @Override + public void slot(String name, Action action) { + var slot = outgoingVariant.getSlots().maybeCreate(name); + + configureSlot(slot, action); + } + + @Override + public void primarySlot(String name, Action action) { + var slot = outgoingVariant.getSlots().maybeCreate(name); + outgoingVariant.getPrimarySlot().set(slot); + + configureSlot(slot, action); + } + + private void configureSlot(Slot slot, Action action) { + var artifactSlot = new ArtifactSlot(outgoingVariant.getVariant(), slot); + + var inputsAssembler = assemblyBuilder.slotAssembler(artifactSlot); + + var spec = new DefaultArtifactAssemblySpec(objectFactory, inputsAssembler.consumer()); + action.execute(spec); + } + +} diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/SlotInputsAssembler.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/SlotInputsAssembler.java --- a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/SlotInputsAssembler.java +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/SlotInputsAssembler.java @@ -1,22 +1,51 @@ package org.implab.gradle.variants.artifacts.internal; -import java.util.Collection; +import java.util.HashSet; import java.util.Set; -import java.util.function.Consumer; -import java.util.function.Function; +import org.gradle.api.file.ConfigurableFileCollection; import org.implab.gradle.variants.artifacts.ArtifactSlot; -import org.implab.gradle.variants.core.VariantsView; import org.implab.gradle.variants.sources.CompileUnit; -import org.implab.gradle.variants.sources.VariantSourcesContext; +import org.implab.gradle.variants.sources.CompileUnitsView; +import org.implab.gradle.variants.sources.RoleProjectionsView; +import org.implab.gradle.variants.sources.SourceSetMaterializer; +/** + * Сборщик входящих элементов из разных источников {@link SlotContribution}. + * Хранит {@link ConfigurableFileCollection} и добавляет в него новые элементы, + * при этом делается дедупликация по {@link SlotInputKey}. + * + */ public class SlotInputsAssembler implements SlotContributionVisitor { - VariantsView variantView; - VariantSourcesContext sources; - ArtifactSlot artifactSlot; + // sources context + private final CompileUnitsView compileUnitsView; + + private final RoleProjectionsView roleProjectionsView; + + private final SourceSetMaterializer sourceSetMaterializer; + + // artifact slot for this assembly + private final ArtifactSlot artifactSlot; + + // content for this assembly + private final ConfigurableFileCollection artifactInputs; - Set seen; + // seen inputs, used for deduplication + private final Set seen = new HashSet<>(); + + public SlotInputsAssembler( + ArtifactSlot artifactSlot, + ConfigurableFileCollection artifactInputs, + CompileUnitsView compileUnitsView, + RoleProjectionsView roleProjectionsView, + SourceSetMaterializer sourceSetMaterializer) { + this.compileUnitsView = compileUnitsView; + this.roleProjectionsView = roleProjectionsView; + this.sourceSetMaterializer = sourceSetMaterializer; + this.artifactSlot = artifactSlot; + this.artifactInputs = artifactInputs; + } @Override public void visit(DirectContribution contribution) { @@ -27,29 +56,41 @@ public class SlotInputsAssembler impleme @Override public void visit(VariantOutputsContribution contribution) { - sources.getCompileUnits().getUnitsForVariant(artifactSlot.variant()).stream() + var units = compileUnitsView.getUnitsForVariant(artifactSlot.variant()); + contributeCompileUnits(units, contribution.outputs()); } @Override public void visit(RoleOutputsContribution contribution) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'visit'"); + var roleProjection = roleProjectionsView.requireProjection(artifactSlot.variant(), + contribution.role()); + var units = roleProjectionsView.getUnits(roleProjection); + + contributeCompileUnits(units, contribution.outputs()); + } @Override public void visit(LayerOutputsContribution contribution) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'visit'"); + var unit = compileUnitsView.requireUnit(artifactSlot.variant(), contribution.layer()); + contributeCompileUnits(Set.of(unit), contribution.outputs()); } - private void contribute(SlotInputKey key, Object input) { - + private void contributeCompileUnits(Set units, Set outputs) { + units.stream() + // expand variant compile units, make (compileUnit, outputName) pairs + .flatMap(unit -> outputs.stream() + .map(output -> new CompileUnitOutputKey(unit, output))) + .forEach(key -> contribute( + key, + sourceSetMaterializer.getSourceSet(key.unit()) + .map(s -> s.output(key.outputName())))); } - private Function resolveOutputs(Collection outputs) { - return unit -> { - - }; + private void contribute(SlotInputKey key, Object resolvedInput) { + if (!seen.add(key)) + return; + artifactInputs.from(resolvedInput); } } diff --git a/variants/src/main/java/org/implab/gradle/variants/sources/RoleProjectionsView.java b/variants/src/main/java/org/implab/gradle/variants/sources/RoleProjectionsView.java --- a/variants/src/main/java/org/implab/gradle/variants/sources/RoleProjectionsView.java +++ b/variants/src/main/java/org/implab/gradle/variants/sources/RoleProjectionsView.java @@ -64,7 +64,7 @@ public final class RoleProjectionsView { } - public RoleProjection getProjection(Variant variant, Role role) { + public RoleProjection requireProjection(Variant variant, Role role) { return findProjection(variant, role) .orElseThrow(() -> new IllegalArgumentException( "Role projection for variant '" + variant.getName() diff --git a/variants/src/test/java/org/implab/gradle/variants/sources/RoleProjectionsViewTest.java b/variants/src/test/java/org/implab/gradle/variants/sources/RoleProjectionsViewTest.java --- a/variants/src/test/java/org/implab/gradle/variants/sources/RoleProjectionsViewTest.java +++ b/variants/src/test/java/org/implab/gradle/variants/sources/RoleProjectionsViewTest.java @@ -33,8 +33,8 @@ class RoleProjectionsViewTest { new VariantRoleLayer(browser, qa, test))); var projections = RoleProjectionsView.of(view); - var productionProjection = projections.getProjection(browser, production); - var qaProjection = projections.getProjection(browser, qa); + var productionProjection = projections.requireProjection(browser, production); + var qaProjection = projections.requireProjection(browser, qa); assertEquals(Set.of(productionProjection, qaProjection), projections.getProjections()); assertEquals(Set.of(productionProjection, qaProjection), projections.getProjectionsForVariant(browser)); @@ -60,7 +60,7 @@ class RoleProjectionsViewTest { var projections = RoleProjectionsView.of(view); - var ex = assertThrows(IllegalArgumentException.class, () -> projections.getProjection(node, production)); + var ex = assertThrows(IllegalArgumentException.class, () -> projections.requireProjection(node, production)); assertTrue(ex.getMessage().contains("Role projection for variant 'node' and role 'production' not found")); }