| @@ -18,7 +18,6 variants { | |||||
| 18 |
|
18 | |||
| 19 | variant('browser') { |
|
19 | variant('browser') { | |
| 20 | role('main') { layers('mainBase', 'mainAmd') } |
|
20 | role('main') { layers('mainBase', 'mainAmd') } | |
| 21 | link('mainBase', 'mainAmd', 'ts:api') |
|
|||
| 22 | } |
|
21 | } | |
| 23 | } |
|
22 | } | |
| 24 |
|
23 | |||
| @@ -54,13 +53,12 variantSources { | |||||
| 54 | ### variants |
|
53 | ### variants | |
| 55 |
|
54 | |||
| 56 | `variants` задает структуру пространства сборки: какие есть слои, какие роли |
|
55 | `variants` задает структуру пространства сборки: какие есть слои, какие роли | |
| 57 |
используют эти слои в каждом варианте, какие |
|
56 | используют эти слои в каждом варианте, какие есть атрибуты и artifact slots. | |
| 58 |
|
|
57 | Модель не создает задачи и не привязана к TS/JS. | |
| 59 |
|
58 | |||
| 60 | Практический смысл: |
|
59 | Практический смысл: | |
| 61 |
|
60 | |||
| 62 | - формализовать архитектуру сборки; |
|
61 | - формализовать архитектуру сборки; | |
| 63 | - централизовать валидацию связей; |
|
|||
| 64 | - дать адаптерам единый источник правды. |
|
62 | - дать адаптерам единый источник правды. | |
| 65 |
|
63 | |||
| 66 | ### sources |
|
64 | ### sources | |
| @@ -90,9 +88,8 outputs. Это уже "физический" уровень, к которому удобно привязывать задачи, | |||||
| 90 | ## DOMAIN MODEL |
|
88 | ## DOMAIN MODEL | |
| 91 |
|
89 | |||
| 92 | - `BuildLayer` — глобальный идентификатор слоя. |
|
90 | - `BuildLayer` — глобальный идентификатор слоя. | |
| 93 |
- `BuildVariant` — агрегат ролей, |
|
91 | - `BuildVariant` — агрегат ролей, атрибутов, артефактных слотов. | |
| 94 | - `BuildRole` — роль внутри варианта, содержит ссылки на layer names. |
|
92 | - `BuildRole` — роль внутри варианта, содержит ссылки на layer names. | |
| 95 | - `LayerLink` — ориентированная связь `from -> to` в графе определенного `kind`. |
|
|||
| 96 | - `GenericSourceSet` — зарегистрированный набор исходников и outputs. |
|
93 | - `GenericSourceSet` — зарегистрированный набор исходников и outputs. | |
| 97 | - `BuildLayerBinding` — правила registration source set для конкретного layer. |
|
94 | - `BuildLayerBinding` — правила registration source set для конкретного layer. | |
| 98 | - `SourceSetContext` — контекст callback-событий registration. |
|
95 | - `SourceSetContext` — контекст callback-событий registration. | |
| @@ -115,7 +112,7 Closure callbacks работают в delegate-first режиме (`@DelegatesTo`). Для | |||||
| 115 | - `GenericSourceSet` — модель источников/outputs для конкретного имени. |
|
112 | - `GenericSourceSet` — модель источников/outputs для конкретного имени. | |
| 116 | - `VariantsPlugin` — регистрирует extension `variants` и lifecycle finalize. |
|
113 | - `VariantsPlugin` — регистрирует extension `variants` и lifecycle finalize. | |
| 117 | - `BuildVariantsExtension` — корневой API модели вариантов. |
|
114 | - `BuildVariantsExtension` — корневой API модели вариантов. | |
| 118 |
- `BuildVariant` — API ролей, |
|
115 | - `BuildVariant` — API ролей, attributes и artifact slots варианта. | |
| 119 | - `VariantsSourcesPlugin` — применяет `variants` + `sources` и запускает адаптер. |
|
116 | - `VariantsSourcesPlugin` — применяет `variants` + `sources` и запускает адаптер. | |
| 120 | - `VariantSourcesExtension` — API bind/events registration. |
|
117 | - `VariantSourcesExtension` — API bind/events registration. | |
| 121 | - `BuildLayerBinding` — слой-конкретный DSL для имени и конфигурации source set. |
|
118 | - `BuildLayerBinding` — слой-конкретный DSL для имени и конфигурации source set. | |
| @@ -10,6 +10,8 public class Strings { | |||||
| 10 |
|
10 | |||
| 11 | private static final Pattern firstLetter = Pattern.compile("^\\w"); |
|
11 | private static final Pattern firstLetter = Pattern.compile("^\\w"); | |
| 12 |
|
12 | |||
|
|
13 | private static final Pattern INVALID_NAME_CHAR = Pattern.compile("[^A-Za-z0-9_.-]"); | |||
|
|
14 | ||||
| 13 | public static String capitalize(String string) { |
|
15 | public static String capitalize(String string) { | |
| 14 | return string == null ? null |
|
16 | return string == null ? null | |
| 15 | : string.length() == 0 ? string |
|
17 | : string.length() == 0 ? string | |
| @@ -43,6 +45,15 public class Strings { | |||||
| 43 | throw new IllegalArgumentException(String.format("Argument %s can't be null or empty", argumentName)); |
|
45 | throw new IllegalArgumentException(String.format("Argument %s can't be null or empty", argumentName)); | |
| 44 | } |
|
46 | } | |
| 45 |
|
47 | |||
|
|
48 | public static void argumentNotNullOrBlank(String value, String argumentName) { | |||
|
|
49 | if (value == null || value.trim().length() == 0) | |||
|
|
50 | throw new IllegalArgumentException(String.format("Argument %s can't be null or blank", argumentName)); | |||
|
|
51 | } | |||
|
|
52 | ||||
|
|
53 | public static String sanitizeName(String value) { | |||
|
|
54 | return INVALID_NAME_CHAR.matcher(value).replaceAll("_"); | |||
|
|
55 | } | |||
|
|
56 | ||||
| 46 | public static String asString(Object value) { |
|
57 | public static String asString(Object value) { | |
| 47 | if (value == null) |
|
58 | if (value == null) | |
| 48 | return null; |
|
59 | return null; | |
| @@ -1,10 +1,8 | |||||
| 1 | package org.implab.gradle.common.sources; |
|
1 | package org.implab.gradle.common.sources; | |
| 2 |
|
2 | |||
| 3 | import java.util.ArrayList; |
|
|||
| 4 | import java.util.Collection; |
|
3 | import java.util.Collection; | |
| 5 | import java.util.Collections; |
|
4 | import java.util.Collections; | |
| 6 | import java.util.LinkedHashMap; |
|
5 | import java.util.LinkedHashMap; | |
| 7 | import java.util.List; |
|
|||
| 8 | import java.util.Optional; |
|
6 | import java.util.Optional; | |
| 9 |
|
7 | |||
| 10 | import javax.inject.Inject; |
|
8 | import javax.inject.Inject; | |
| @@ -30,7 +28,6 public abstract class BuildVariant imple | |||||
| 30 | */ |
|
28 | */ | |
| 31 | private final VariantAttributes attributes; |
|
29 | private final VariantAttributes attributes; | |
| 32 | private final LinkedHashMap<String, BuildRole> roles = new LinkedHashMap<>(); |
|
30 | private final LinkedHashMap<String, BuildRole> roles = new LinkedHashMap<>(); | |
| 33 | private final List<LayerLink> links = new ArrayList<>(); |
|
|||
| 34 | private final LinkedHashMap<String, BuildArtifactSlot> artifactSlots = new LinkedHashMap<>(); |
|
31 | private final LinkedHashMap<String, BuildArtifactSlot> artifactSlots = new LinkedHashMap<>(); | |
| 35 |
|
32 | |||
| 36 | @Inject |
|
33 | @Inject | |
| @@ -110,29 +107,6 public abstract class BuildVariant imple | |||||
| 110 | "Variant '" + this.name + "' doesn't define role '" + name + "'")); |
|
107 | "Variant '" + this.name + "' doesn't define role '" + name + "'")); | |
| 111 | } |
|
108 | } | |
| 112 |
|
109 | |||
| 113 | public Collection<LayerLink> getLinks() { |
|
|||
| 114 | return Collections.unmodifiableList(links); |
|
|||
| 115 | } |
|
|||
| 116 |
|
||||
| 117 | public void links(Action<? super LinksSpec> action) { |
|
|||
| 118 | ensureMutable("configure links"); |
|
|||
| 119 | action.execute(new LinksSpec()); |
|
|||
| 120 | } |
|
|||
| 121 |
|
||||
| 122 | public void links(Closure<?> configure) { |
|
|||
| 123 | links(Closures.action(configure)); |
|
|||
| 124 | } |
|
|||
| 125 |
|
||||
| 126 | public LayerLink link(String from, String to, String kind) { |
|
|||
| 127 | ensureMutable("add links"); |
|
|||
| 128 | var link = new LayerLink( |
|
|||
| 129 | requireLinkValue("from", from), |
|
|||
| 130 | requireLinkValue("to", to), |
|
|||
| 131 | requireLinkValue("kind", kind)); |
|
|||
| 132 | links.add(link); |
|
|||
| 133 | return link; |
|
|||
| 134 | } |
|
|||
| 135 |
|
||||
| 136 | public Collection<BuildArtifactSlot> getArtifactSlots() { |
|
110 | public Collection<BuildArtifactSlot> getArtifactSlots() { | |
| 137 | return Collections.unmodifiableCollection(artifactSlots.values()); |
|
111 | return Collections.unmodifiableCollection(artifactSlots.values()); | |
| 138 | } |
|
112 | } | |
| @@ -196,13 +170,6 public abstract class BuildVariant imple | |||||
| 196 | throw new InvalidUserDataException("Variant '" + name + "' is finalized and cannot " + operation); |
|
170 | throw new InvalidUserDataException("Variant '" + name + "' is finalized and cannot " + operation); | |
| 197 | } |
|
171 | } | |
| 198 |
|
172 | |||
| 199 | private static String requireLinkValue(String field, String value) { |
|
|||
| 200 | if (value == null || value.trim().isEmpty()) |
|
|||
| 201 | throw new InvalidUserDataException("Link '" + field + "' must not be null or blank"); |
|
|||
| 202 |
|
||||
| 203 | return value.trim(); |
|
|||
| 204 | } |
|
|||
| 205 |
|
||||
| 206 | public final class RolesSpec { |
|
173 | public final class RolesSpec { | |
| 207 | public BuildRole role(String name, Action<? super BuildRole> configure) { |
|
174 | public BuildRole role(String name, Action<? super BuildRole> configure) { | |
| 208 | return BuildVariant.this.role(name, configure); |
|
175 | return BuildVariant.this.role(name, configure); | |
| @@ -229,16 +196,6 public abstract class BuildVariant imple | |||||
| 229 | } |
|
196 | } | |
| 230 | } |
|
197 | } | |
| 231 |
|
198 | |||
| 232 | public final class LinksSpec { |
|
|||
| 233 | public LayerLink link(String from, String to, String kind) { |
|
|||
| 234 | return BuildVariant.this.link(from, to, kind); |
|
|||
| 235 | } |
|
|||
| 236 |
|
||||
| 237 | public Collection<LayerLink> getAll() { |
|
|||
| 238 | return BuildVariant.this.getLinks(); |
|
|||
| 239 | } |
|
|||
| 240 | } |
|
|||
| 241 |
|
||||
| 242 | public final class ArtifactSlotsSpec { |
|
199 | public final class ArtifactSlotsSpec { | |
| 243 | public BuildArtifactSlot artifactSlot(String name, Action<? super BuildArtifactSlot> configure) { |
|
200 | public BuildArtifactSlot artifactSlot(String name, Action<? super BuildArtifactSlot> configure) { | |
| 244 | return BuildVariant.this.artifactSlot(name, configure); |
|
201 | return BuildVariant.this.artifactSlot(name, configure); | |
| @@ -3,14 +3,11 package org.implab.gradle.common.sources | |||||
| 3 | import java.util.ArrayList; |
|
3 | import java.util.ArrayList; | |
| 4 | import java.util.Collection; |
|
4 | import java.util.Collection; | |
| 5 | import java.util.Collections; |
|
5 | import java.util.Collections; | |
| 6 | import java.util.HashMap; |
|
|||
| 7 | import java.util.HashSet; |
|
|||
| 8 | import java.util.LinkedHashMap; |
|
6 | import java.util.LinkedHashMap; | |
| 9 | import java.util.LinkedHashSet; |
|
7 | import java.util.LinkedHashSet; | |
| 10 | import java.util.List; |
|
8 | import java.util.List; | |
| 11 | import java.util.Map; |
|
9 | import java.util.Map; | |
| 12 | import java.util.Optional; |
|
10 | import java.util.Optional; | |
| 13 | import java.util.Set; |
|
|||
| 14 |
|
11 | |||
| 15 | import javax.inject.Inject; |
|
12 | import javax.inject.Inject; | |
| 16 |
|
13 | |||
| @@ -197,8 +194,7 public abstract class BuildVariantsExten | |||||
| 197 | } |
|
194 | } | |
| 198 |
|
195 | |||
| 199 | validateRoleAndArtifactNames(variant, errors); |
|
196 | validateRoleAndArtifactNames(variant, errors); | |
| 200 |
|
|
197 | validateRoleMappings(variant, layersByName, errors); | |
| 201 | validateLinks(variant, variantLayers, errors); |
|
|||
| 202 | } |
|
198 | } | |
| 203 |
|
199 | |||
| 204 | private static void validateRoleAndArtifactNames(BuildVariant variant, List<String> errors) { |
|
200 | private static void validateRoleAndArtifactNames(BuildVariant variant, List<String> errors) { | |
| @@ -227,10 +223,8 public abstract class BuildVariantsExten | |||||
| 227 | } |
|
223 | } | |
| 228 | } |
|
224 | } | |
| 229 |
|
225 | |||
| 230 |
private static |
|
226 | private static void validateRoleMappings(BuildVariant variant, Map<String, BuildLayer> layersByName, | |
| 231 | List<String> errors) { |
|
227 | List<String> errors) { | |
| 232 | var variantLayers = new LinkedHashSet<String>(); |
|
|||
| 233 |
|
||||
| 234 | for (var role : variant.getRoles()) { |
|
228 | for (var role : variant.getRoles()) { | |
| 235 | var seenLayers = new LinkedHashSet<String>(); |
|
229 | var seenLayers = new LinkedHashSet<String>(); | |
| 236 | for (var layerName : role.getLayers().getOrElse(List.of())) { |
|
230 | for (var layerName : role.getLayers().getOrElse(List.of())) { | |
| @@ -249,88 +243,9 public abstract class BuildVariantsExten | |||||
| 249 | if (!seenLayers.add(normalizedLayerName)) { |
|
243 | if (!seenLayers.add(normalizedLayerName)) { | |
| 250 | errors.add("Variant '" + variant.getName() + "', role '" + role.getName() |
|
244 | errors.add("Variant '" + variant.getName() + "', role '" + role.getName() | |
| 251 | + "' contains duplicated layer reference '" + normalizedLayerName + "'"); |
|
245 | + "' contains duplicated layer reference '" + normalizedLayerName + "'"); | |
| 252 | continue; |
|
|||
| 253 | } |
|
246 | } | |
| 254 |
|
||||
| 255 | variantLayers.add(normalizedLayerName); |
|
|||
| 256 | } |
|
247 | } | |
| 257 | } |
|
248 | } | |
| 258 |
|
||||
| 259 | return variantLayers; |
|
|||
| 260 | } |
|
|||
| 261 |
|
||||
| 262 | private static void validateLinks(BuildVariant variant, Set<String> variantLayers, List<String> errors) { |
|
|||
| 263 | var seenLinks = new HashSet<String>(); |
|
|||
| 264 | var edgesByKind = new HashMap<String, Map<String, Set<String>>>(); |
|
|||
| 265 |
|
||||
| 266 | for (var link : variant.getLinks()) { |
|
|||
| 267 | var from = normalize(link.from()); |
|
|||
| 268 | var to = normalize(link.to()); |
|
|||
| 269 | var kind = normalize(link.kind()); |
|
|||
| 270 |
|
||||
| 271 | if (from == null || to == null || kind == null) { |
|
|||
| 272 | errors.add("Variant '" + variant.getName() + "' has incomplete link (from/to/kind are required)"); |
|
|||
| 273 | continue; |
|
|||
| 274 | } |
|
|||
| 275 |
|
||||
| 276 | if (!variantLayers.contains(from)) { |
|
|||
| 277 | errors.add("Variant '" + variant.getName() + "' link references unknown source layer '" |
|
|||
| 278 | + from + "'"); |
|
|||
| 279 | continue; |
|
|||
| 280 | } |
|
|||
| 281 |
|
||||
| 282 | if (!variantLayers.contains(to)) { |
|
|||
| 283 | errors.add("Variant '" + variant.getName() + "' link references unknown target layer '" |
|
|||
| 284 | + to + "'"); |
|
|||
| 285 | continue; |
|
|||
| 286 | } |
|
|||
| 287 |
|
||||
| 288 | var linkKey = from + "\u0000" + to + "\u0000" + kind; |
|
|||
| 289 | if (!seenLinks.add(linkKey)) { |
|
|||
| 290 | errors.add("Variant '" + variant.getName() + "' has duplicated link tuple (from='" + from |
|
|||
| 291 | + "', to='" + to + "', kind='" + kind + "')"); |
|
|||
| 292 | } |
|
|||
| 293 |
|
||||
| 294 | edgesByKind |
|
|||
| 295 | .computeIfAbsent(kind, x -> new LinkedHashMap<>()) |
|
|||
| 296 | .computeIfAbsent(from, x -> new LinkedHashSet<>()) |
|
|||
| 297 | .add(to); |
|
|||
| 298 | } |
|
|||
| 299 |
|
||||
| 300 | for (var entry : edgesByKind.entrySet()) { |
|
|||
| 301 | if (hasCycle(variantLayers, entry.getValue())) { |
|
|||
| 302 | errors.add("Variant '" + variant.getName() + "' contains cycle in links with kind '" + entry.getKey() + "'"); |
|
|||
| 303 | } |
|
|||
| 304 | } |
|
|||
| 305 | } |
|
|||
| 306 |
|
||||
| 307 | private static boolean hasCycle(Set<String> nodes, Map<String, Set<String>> edges) { |
|
|||
| 308 | var state = new HashMap<String, Integer>(); |
|
|||
| 309 |
|
||||
| 310 | for (var node : nodes) { |
|
|||
| 311 | if (dfs(node, state, edges)) |
|
|||
| 312 | return true; |
|
|||
| 313 | } |
|
|||
| 314 |
|
||||
| 315 | return false; |
|
|||
| 316 | } |
|
|||
| 317 |
|
||||
| 318 | private static boolean dfs(String node, Map<String, Integer> state, Map<String, Set<String>> edges) { |
|
|||
| 319 | var current = state.getOrDefault(node, 0); |
|
|||
| 320 | if (current == 1) |
|
|||
| 321 | return true; |
|
|||
| 322 | if (current == 2) |
|
|||
| 323 | return false; |
|
|||
| 324 |
|
||||
| 325 | state.put(node, 1); |
|
|||
| 326 |
|
||||
| 327 | for (var next : edges.getOrDefault(node, Set.of())) { |
|
|||
| 328 | if (dfs(next, state, edges)) |
|
|||
| 329 | return true; |
|
|||
| 330 | } |
|
|||
| 331 |
|
||||
| 332 | state.put(node, 2); |
|
|||
| 333 | return false; |
|
|||
| 334 | } |
|
249 | } | |
| 335 |
|
250 | |||
| 336 | private static String normalize(String value) { |
|
251 | private static String normalize(String value) { | |
| @@ -341,10 +256,6 public abstract class BuildVariantsExten | |||||
| 341 | return trimmed.isEmpty() ? null : trimmed; |
|
256 | return trimmed.isEmpty() ? null : trimmed; | |
| 342 | } |
|
257 | } | |
| 343 |
|
258 | |||
| 344 | private static boolean isBlank(String value) { |
|
|||
| 345 | return normalize(value) == null; |
|
|||
| 346 | } |
|
|||
| 347 |
|
||||
| 348 | private void ensureMutable(String operation) { |
|
259 | private void ensureMutable(String operation) { | |
| 349 | if (finalized) |
|
260 | if (finalized) | |
| 350 | throw new InvalidUserDataException("Variants model is finalized and cannot " + operation); |
|
261 | throw new InvalidUserDataException("Variants model is finalized and cannot " + operation); | |
| @@ -12,6 +12,7 import javax.inject.Inject; | |||||
| 12 | import org.implab.gradle.common.core.lang.Closures; |
|
12 | import org.implab.gradle.common.core.lang.Closures; | |
| 13 | import org.implab.gradle.common.core.lang.Strings; |
|
13 | import org.implab.gradle.common.core.lang.Strings; | |
| 14 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
14 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
|
|
15 | import org.eclipse.jdt.annotation.Nullable; | |||
| 15 | import org.gradle.api.Action; |
|
16 | import org.gradle.api.Action; | |
| 16 | import org.gradle.api.InvalidUserDataException; |
|
17 | import org.gradle.api.InvalidUserDataException; | |
| 17 | import org.gradle.api.NamedDomainObjectContainer; |
|
18 | import org.gradle.api.NamedDomainObjectContainer; | |
| @@ -23,16 +24,16 import org.gradle.api.logging.Logging; | |||||
| 23 | import groovy.lang.Closure; |
|
24 | import groovy.lang.Closure; | |
| 24 | import groovy.lang.DelegatesTo; |
|
25 | import groovy.lang.DelegatesTo; | |
| 25 |
|
26 | |||
|
|
27 | import static org.implab.gradle.common.core.lang.Strings.sanitizeName; | |||
|
|
28 | ||||
| 26 | /** |
|
29 | /** | |
| 27 | * Adapter extension that registers source sets for variant/layer pairs. |
|
30 | * Adapter extension that registers source sets for variant/layer pairs. | |
| 28 | */ |
|
31 | */ | |
| 29 | @NonNullByDefault |
|
32 | @NonNullByDefault | |
| 30 | public abstract class VariantSourcesExtension { |
|
33 | public abstract class VariantSourcesExtension { | |
| 31 | private static final Logger logger = Logging.getLogger(VariantSourcesExtension.class); |
|
34 | private static final Logger logger = Logging.getLogger(VariantSourcesExtension.class); | |
| 32 | private static final Pattern INVALID_NAME_CHAR = Pattern.compile("[^A-Za-z0-9_.-]"); |
|
|||
| 33 | private static final Pattern SOURCE_SET_NAME_TOKEN = Pattern.compile("\\{([A-Za-z][A-Za-z0-9]*)\\}"); |
|
35 | private static final Pattern SOURCE_SET_NAME_TOKEN = Pattern.compile("\\{([A-Za-z][A-Za-z0-9]*)\\}"); | |
| 34 |
|
36 | |||
| 35 | private final ObjectFactory objects; |
|
|||
| 36 | private final NamedDomainObjectContainer<BuildLayerBinding> bindings; |
|
37 | private final NamedDomainObjectContainer<BuildLayerBinding> bindings; | |
| 37 | private final List<Action<? super SourceSetContext>> registeredActions = new ArrayList<>(); |
|
38 | private final List<Action<? super SourceSetContext>> registeredActions = new ArrayList<>(); | |
| 38 | private final List<Action<? super SourceSetContext>> boundActions = new ArrayList<>(); |
|
39 | private final List<Action<? super SourceSetContext>> boundActions = new ArrayList<>(); | |
| @@ -44,7 +45,6 public abstract class VariantSourcesExte | |||||
| 44 |
|
45 | |||
| 45 | @Inject |
|
46 | @Inject | |
| 46 | public VariantSourcesExtension(ObjectFactory objects) { |
|
47 | public VariantSourcesExtension(ObjectFactory objects) { | |
| 47 | this.objects = objects; |
|
|||
| 48 | bindings = objects.domainObjectContainer(BuildLayerBinding.class); |
|
48 | bindings = objects.domainObjectContainer(BuildLayerBinding.class); | |
| 49 | } |
|
49 | } | |
| 50 |
|
50 | |||
| @@ -62,7 +62,7 public abstract class VariantSourcesExte | |||||
| 62 | } |
|
62 | } | |
| 63 |
|
63 | |||
| 64 | public BuildLayerBinding bind(String layer) { |
|
64 | public BuildLayerBinding bind(String layer) { | |
| 65 | return bindings.maybeCreate(normalize(layer)); |
|
65 | return bindings.maybeCreate(normalize(layer, "Layer name must not be null or blank")); | |
| 66 | } |
|
66 | } | |
| 67 |
|
67 | |||
| 68 | /** |
|
68 | /** | |
| @@ -173,7 +173,7 public abstract class VariantSourcesExte | |||||
| 173 | .map(layerName -> new LayerUsage( |
|
173 | .map(layerName -> new LayerUsage( | |
| 174 | variant.getName(), |
|
174 | variant.getName(), | |
| 175 | role.getName(), |
|
175 | role.getName(), | |
| 176 | normalize(layerName))))); |
|
176 | normalize(layerName, "Layer name in variant '" + variant.getName() + "' and role '" + role.getName() + "' must not be null or blank"))))); | |
| 177 | } |
|
177 | } | |
| 178 |
|
178 | |||
| 179 | private void registerLayerUsage(LayerUsage usage, NamedDomainObjectContainer<GenericSourceSet> sources) { |
|
179 | private void registerLayerUsage(LayerUsage usage, NamedDomainObjectContainer<GenericSourceSet> sources) { | |
| @@ -255,7 +255,7 public abstract class VariantSourcesExte | |||||
| 255 | private static String sourceSetName(LayerUsage usage, String pattern) { |
|
255 | private static String sourceSetName(LayerUsage usage, String pattern) { | |
| 256 | var normalizedPattern = normalize(pattern, "sourceSetNamePattern must not be null or blank"); |
|
256 | var normalizedPattern = normalize(pattern, "sourceSetNamePattern must not be null or blank"); | |
| 257 | var resolved = resolveSourceSetNamePattern(normalizedPattern, usage); |
|
257 | var resolved = resolveSourceSetNamePattern(normalizedPattern, usage); | |
| 258 | var result = sanitize(resolved); |
|
258 | var result = sanitizeName(resolved); | |
| 259 |
|
259 | |||
| 260 | if (result.isEmpty()) |
|
260 | if (result.isEmpty()) | |
| 261 | throw new InvalidUserDataException("sourceSetNamePattern '" + pattern + "' resolved to empty source set name"); |
|
261 | throw new InvalidUserDataException("sourceSetNamePattern '" + pattern + "' resolved to empty source set name"); | |
| @@ -278,26 +278,18 public abstract class VariantSourcesExte | |||||
| 278 |
|
278 | |||
| 279 | private static String tokenValue(String token, LayerUsage usage) { |
|
279 | private static String tokenValue(String token, LayerUsage usage) { | |
| 280 | return switch (token) { |
|
280 | return switch (token) { | |
| 281 | case "variant" -> sanitize(usage.variantName()); |
|
281 | case "variant" -> sanitizeName(usage.variantName()); | |
| 282 | case "variantCap" -> Strings.capitalize(sanitize(usage.variantName())); |
|
282 | case "variantCap" -> Strings.capitalize(sanitizeName(usage.variantName())); | |
| 283 | case "role" -> sanitize(usage.roleName()); |
|
283 | case "role" -> sanitizeName(usage.roleName()); | |
| 284 | case "roleCap" -> Strings.capitalize(sanitize(usage.roleName())); |
|
284 | case "roleCap" -> Strings.capitalize(sanitizeName(usage.roleName())); | |
| 285 | case "layer" -> sanitize(usage.layerName()); |
|
285 | case "layer" -> sanitizeName(usage.layerName()); | |
| 286 | case "layerCap" -> Strings.capitalize(sanitize(usage.layerName())); |
|
286 | case "layerCap" -> Strings.capitalize(sanitizeName(usage.layerName())); | |
| 287 | default -> throw new InvalidUserDataException( |
|
287 | default -> throw new InvalidUserDataException( | |
| 288 | "sourceSetNamePattern contains unsupported token '{" + token + "}'"); |
|
288 | "sourceSetNamePattern contains unsupported token '{" + token + "}'"); | |
| 289 | }; |
|
289 | }; | |
| 290 | } |
|
290 | } | |
| 291 |
|
291 | |||
| 292 |
private static String |
|
292 | private static String normalize(@Nullable String value, String errorMessage) { | |
| 293 | return INVALID_NAME_CHAR.matcher(value).replaceAll("_"); |
|
|||
| 294 | } |
|
|||
| 295 |
|
||||
| 296 | private static String normalize(String value) { |
|
|||
| 297 | return normalize(value, "Value must not be null or blank"); |
|
|||
| 298 | } |
|
|||
| 299 |
|
||||
| 300 | private static String normalize(String value, String errorMessage) { |
|
|||
| 301 | if (value == null) |
|
293 | if (value == null) | |
| 302 | throw new InvalidUserDataException(errorMessage); |
|
294 | throw new InvalidUserDataException(errorMessage); | |
| 303 | var trimmed = value.trim(); |
|
295 | var trimmed = value.trim(); | |
| @@ -48,7 +48,6 class VariantsPluginFunctionalTest { | |||||
| 48 | role('main') { |
|
48 | role('main') { | |
| 49 | layers('mainBase', 'mainAmd') |
|
49 | layers('mainBase', 'mainAmd') | |
| 50 | } |
|
50 | } | |
| 51 | link('mainBase', 'mainAmd', 'ts:api') |
|
|||
| 52 | artifactSlot('mainCompiled') |
|
51 | artifactSlot('mainCompiled') | |
| 53 | } |
|
52 | } | |
| 54 | } |
|
53 | } | |
| @@ -58,7 +57,6 class VariantsPluginFunctionalTest { | |||||
| 58 | def browser = variants.require('browser') |
|
57 | def browser = variants.require('browser') | |
| 59 | println('attributes=' + browser.attributes.size()) |
|
58 | println('attributes=' + browser.attributes.size()) | |
| 60 | println('roles=' + browser.roles.size()) |
|
59 | println('roles=' + browser.roles.size()) | |
| 61 | println('links=' + browser.links.size()) |
|
|||
| 62 | println('slots=' + browser.artifactSlots.size()) |
|
60 | println('slots=' + browser.artifactSlots.size()) | |
| 63 | } |
|
61 | } | |
| 64 | } |
|
62 | } | |
| @@ -68,7 +66,6 class VariantsPluginFunctionalTest { | |||||
| 68 |
|
66 | |||
| 69 | assertTrue(result.getOutput().contains("attributes=2")); |
|
67 | assertTrue(result.getOutput().contains("attributes=2")); | |
| 70 | assertTrue(result.getOutput().contains("roles=1")); |
|
68 | assertTrue(result.getOutput().contains("roles=1")); | |
| 71 | assertTrue(result.getOutput().contains("links=1")); |
|
|||
| 72 | assertTrue(result.getOutput().contains("slots=1")); |
|
69 | assertTrue(result.getOutput().contains("slots=1")); | |
| 73 | assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS); |
|
70 | assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS); | |
| 74 | } |
|
71 | } | |
| @@ -94,28 +91,6 class VariantsPluginFunctionalTest { | |||||
| 94 | } |
|
91 | } | |
| 95 |
|
92 | |||
| 96 | @Test |
|
93 | @Test | |
| 97 | void failsOnCycleInLinksByKind() throws Exception { |
|
|||
| 98 | assertBuildFails(""" |
|
|||
| 99 | plugins { |
|
|||
| 100 | id 'org.implab.gradle-variants' |
|
|||
| 101 | } |
|
|||
| 102 |
|
||||
| 103 | variants { |
|
|||
| 104 | layer('a') |
|
|||
| 105 | layer('b') |
|
|||
| 106 |
|
||||
| 107 | variant('browser') { |
|
|||
| 108 | role('main') { |
|
|||
| 109 | layers('a', 'b') |
|
|||
| 110 | } |
|
|||
| 111 | link('a', 'b', 'ts:api') |
|
|||
| 112 | link('b', 'a', 'ts:api') |
|
|||
| 113 | } |
|
|||
| 114 | } |
|
|||
| 115 | """, "contains cycle in links with kind 'ts:api'"); |
|
|||
| 116 | } |
|
|||
| 117 |
|
||||
| 118 | @Test |
|
|||
| 119 | void allowsUsingLayerFromDifferentVariantRole() throws Exception { |
|
94 | void allowsUsingLayerFromDifferentVariantRole() throws Exception { | |
| 120 | writeFile(SETTINGS_FILE, ROOT_NAME); |
|
95 | writeFile(SETTINGS_FILE, ROOT_NAME); | |
| 121 | writeFile(BUILD_FILE, """ |
|
96 | writeFile(BUILD_FILE, """ | |
| @@ -139,91 +114,6 class VariantsPluginFunctionalTest { | |||||
| 139 | } |
|
114 | } | |
| 140 |
|
115 | |||
| 141 | @Test |
|
116 | @Test | |
| 142 | void failsOnIncompleteLink() throws Exception { |
|
|||
| 143 | assertBuildFails(""" |
|
|||
| 144 | plugins { |
|
|||
| 145 | id 'org.implab.gradle-variants' |
|
|||
| 146 | } |
|
|||
| 147 |
|
||||
| 148 | variants { |
|
|||
| 149 | layer('a') |
|
|||
| 150 | layer('b') |
|
|||
| 151 |
|
||||
| 152 | variant('browser') { |
|
|||
| 153 | role('main') { |
|
|||
| 154 | layers('a', 'b') |
|
|||
| 155 | } |
|
|||
| 156 | link('a', 'b', null) |
|
|||
| 157 | } |
|
|||
| 158 | } |
|
|||
| 159 | """, "Link 'kind' must not be null or blank"); |
|
|||
| 160 | } |
|
|||
| 161 |
|
||||
| 162 | @Test |
|
|||
| 163 | void failsOnDuplicatedLinkTuple() throws Exception { |
|
|||
| 164 | assertBuildFails(""" |
|
|||
| 165 | plugins { |
|
|||
| 166 | id 'org.implab.gradle-variants' |
|
|||
| 167 | } |
|
|||
| 168 |
|
||||
| 169 | variants { |
|
|||
| 170 | layer('a') |
|
|||
| 171 | layer('b') |
|
|||
| 172 |
|
||||
| 173 | variant('browser') { |
|
|||
| 174 | role('main') { |
|
|||
| 175 | layers('a', 'b') |
|
|||
| 176 | } |
|
|||
| 177 | link('a', 'b', 'ts:api') |
|
|||
| 178 | link('a', 'b', 'ts:api') |
|
|||
| 179 | } |
|
|||
| 180 | } |
|
|||
| 181 | """, "has duplicated link tuple (from='a', to='b', kind='ts:api')"); |
|
|||
| 182 | } |
|
|||
| 183 |
|
||||
| 184 | @Test |
|
|||
| 185 | void failsOnUnknownSourceLayerInLink() throws Exception { |
|
|||
| 186 | assertBuildFails(""" |
|
|||
| 187 | plugins { |
|
|||
| 188 | id 'org.implab.gradle-variants' |
|
|||
| 189 | } |
|
|||
| 190 |
|
||||
| 191 | variants { |
|
|||
| 192 | layer('a') { |
|
|||
| 193 | } |
|
|||
| 194 |
|
||||
| 195 | variant('browser') { |
|
|||
| 196 | role('main') { |
|
|||
| 197 | layers('a') |
|
|||
| 198 | } |
|
|||
| 199 | link('missing', 'a', 'ts:api') |
|
|||
| 200 | } |
|
|||
| 201 | } |
|
|||
| 202 | """, "references unknown source layer 'missing'"); |
|
|||
| 203 | } |
|
|||
| 204 |
|
||||
| 205 | @Test |
|
|||
| 206 | void failsOnUnknownTargetLayerInLink() throws Exception { |
|
|||
| 207 | assertBuildFails(""" |
|
|||
| 208 | plugins { |
|
|||
| 209 | id 'org.implab.gradle-variants' |
|
|||
| 210 | } |
|
|||
| 211 |
|
||||
| 212 | variants { |
|
|||
| 213 | layer('a') { |
|
|||
| 214 | } |
|
|||
| 215 |
|
||||
| 216 | variant('browser') { |
|
|||
| 217 | role('main') { |
|
|||
| 218 | layers('a') |
|
|||
| 219 | } |
|
|||
| 220 | link('a', 'missing', 'ts:api') |
|
|||
| 221 | } |
|
|||
| 222 | } |
|
|||
| 223 | """, "references unknown target layer 'missing'"); |
|
|||
| 224 | } |
|
|||
| 225 |
|
||||
| 226 | @Test |
|
|||
| 227 | void failsOnDuplicatedLayerReferenceInRole() throws Exception { |
|
117 | void failsOnDuplicatedLayerReferenceInRole() throws Exception { | |
| 228 | assertBuildFails(""" |
|
118 | assertBuildFails(""" | |
| 229 | plugins { |
|
119 | plugins { | |
| @@ -25,7 +25,6 variants { | |||||
| 25 | layers('mainBase', 'mainAmd') |
|
25 | layers('mainBase', 'mainAmd') | |
| 26 | } |
|
26 | } | |
| 27 |
|
27 | |||
| 28 | link('mainBase', 'mainAmd', 'ts:api') |
|
|||
| 29 | artifactSlot('mainCompiled') |
|
28 | artifactSlot('mainCompiled') | |
| 30 | } |
|
29 | } | |
| 31 | } |
|
30 | } | |
| @@ -39,31 +38,18 variants { | |||||
| 39 | ### layers |
|
38 | ### layers | |
| 40 |
|
39 | |||
| 41 | Глобальные логические слои. Служат единым словарем имен, на которые затем |
|
40 | Глобальные логические слои. Служат единым словарем имен, на которые затем | |
| 42 |
ссылаются роли |
|
41 | ссылаются роли. | |
| 43 |
|
42 | |||
| 44 | ### variants |
|
43 | ### variants | |
| 45 |
|
44 | |||
| 46 | Именованные варианты исполнения/пакетирования (`browser`, `node`, и т.д.). |
|
45 | Именованные варианты исполнения/пакетирования (`browser`, `node`, и т.д.). | |
| 47 |
Вариант агрегирует роли, |
|
46 | Вариант агрегирует роли, атрибуты и artifact slots. | |
| 48 |
|
47 | |||
| 49 | ### roles |
|
48 | ### roles | |
| 50 |
|
49 | |||
| 51 | Роль описывает набор слоев в пределах варианта (`main`, `test`, `tools`). |
|
50 | Роль описывает набор слоев в пределах варианта (`main`, `test`, `tools`). | |
| 52 | Одна роль может ссылаться на несколько слоев. |
|
51 | Одна роль может ссылаться на несколько слоев. | |
| 53 |
|
52 | |||
| 54 | ### links |
|
|||
| 55 |
|
||||
| 56 | `link(from, to, kind)` — ориентированная связь между слоями внутри варианта. |
|
|||
| 57 |
|
||||
| 58 | `kind` задает независимый тип графа (например `ts:api`, `bundle:runtime`). Это |
|
|||
| 59 | позволяет вести несколько параллельных графов зависимостей над теми же слоями. |
|
|||
| 60 |
|
||||
| 61 | Практические сценарии использования `link` в адаптерах: |
|
|||
| 62 |
|
||||
| 63 | - расчет topological order по выбранному `kind`; |
|
|||
| 64 | - wiring task inputs/outputs между слоями; |
|
|||
| 65 | - проверка допустимости дополнительных pipeline-зависимостей. |
|
|||
| 66 |
|
||||
| 67 | ### attributes |
|
53 | ### attributes | |
| 68 |
|
54 | |||
| 69 | Typed-атрибуты (`Attribute<T> -> Provider<T>`) для передачи параметров в |
|
55 | Typed-атрибуты (`Attribute<T> -> Provider<T>`) для передачи параметров в | |
| @@ -80,10 +66,8 Typed-атрибуты (`Attribute<T> -> Provider<T>`) для передачи параметров в | |||||
| 80 |
|
66 | |||
| 81 | - роль не может ссылаться на неизвестный layer; |
|
67 | - роль не может ссылаться на неизвестный layer; | |
| 82 | - пустые имена layer запрещены; |
|
68 | - пустые имена layer запрещены; | |
| 83 | - у link обязательны `from`, `to`, `kind`; |
|
69 | - имена ролей в варианте должны быть уникальны; | |
| 84 | - `from`/`to` должны входить в слойную область варианта; |
|
70 | - имена artifact slots в варианте должны быть уникальны. | |
| 85 | - tuple `(from, to, kind)` должен быть уникален; |
|
|||
| 86 | - циклы в графе одного `kind` запрещены. |
|
|||
| 87 |
|
71 | |||
| 88 | ## LIFECYCLE |
|
72 | ## LIFECYCLE | |
| 89 |
|
73 | |||
| @@ -108,7 +92,6 Typed-атрибуты (`Attribute<T> -> Provider<T>`) для передачи параметров в | |||||
| 108 |
|
92 | |||
| 109 | - `attributes { ... }` — атрибуты варианта (+ sugar `string/bool/integer`). |
|
93 | - `attributes { ... }` — атрибуты варианта (+ sugar `string/bool/integer`). | |
| 110 | - `role(...)`, `roles { ... }` — роли варианта. |
|
94 | - `role(...)`, `roles { ... }` — роли варианта. | |
| 111 | - `link(...)`, `links { ... }` — связи слоев внутри варианта. |
|
|||
| 112 | - `artifactSlot(...)`, `artifactSlots { ... }` — артефактные слоты. |
|
95 | - `artifactSlot(...)`, `artifactSlots { ... }` — артефактные слоты. | |
| 113 |
|
96 | |||
| 114 | ## KEY CLASSES |
|
97 | ## KEY CLASSES | |
| @@ -118,7 +101,6 Typed-атрибуты (`Attribute<T> -> Provider<T>`) для передачи параметров в | |||||
| 118 | - `BuildVariant` — агрегатная модель варианта. |
|
101 | - `BuildVariant` — агрегатная модель варианта. | |
| 119 | - `BuildLayer` — модель слоя. |
|
102 | - `BuildLayer` — модель слоя. | |
| 120 | - `BuildRole` — модель роли. |
|
103 | - `BuildRole` — модель роли. | |
| 121 | - `LayerLink` — модель направленной связи. |
|
|||
| 122 | - `BuildArtifactSlot` — модель артефактного слота. |
|
104 | - `BuildArtifactSlot` — модель артефактного слота. | |
| 123 | - `VariantAttributes` — typed wrapper для variant attributes. |
|
105 | - `VariantAttributes` — typed wrapper для variant attributes. | |
| 124 |
|
106 | |||
| 1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now
