# Variant Artifacts Plugin ## NAME `VariantsArtifactsPlugin` и extension `variantArtifacts`. ## SYNOPSIS ```groovy import org.gradle.api.attributes.Attribute plugins { id 'org.implab.gradle-variants-artifacts' } def variantAttr = Attribute.of('test.variant', String) def slotAttr = Attribute.of('test.slot', String) variants { layer('main') variant('browser') { role('main') { layers('main') } } } variantSources { bind('main') { configureSourceSet { declareOutputs('types', 'js', 'resources') } } } variantArtifacts { variant('browser') { primarySlot('typesPackage') { fromVariant { output('types') } } slot('js') { fromVariant { output('js') } } slot('resources') { fromVariant { output('resources') } } } whenOutgoingVariant { publication -> publication.configureConfiguration { attributes.attribute(variantAttr, publication.variantName()) } publication.primarySlot().configureArtifactAttributes { attribute(slotAttr, publication.primarySlot().slotName()) } publication.requireSlot('js').configureArtifactAttributes { attribute(slotAttr, 'js') } publication.requireSlot('resources').configureArtifactAttributes { attribute(slotAttr, 'resources') } } } ``` ## DESCRIPTION `VariantsArtifactsPlugin` применяет `VariantsSourcesPlugin`, затем строит outgoing publication model поверх `variantSources`. ### publication model Для каждого `variantArtifacts.variant('')` публикуется один outgoing build variant: - primary configuration `Elements`; - primary artifact slot на самой configuration; - secondary variants внутри `configuration.outgoing.variants` для остальных slots. Пример: - `browserElements` - primary slot: `typesPackage` - secondary variants: `js`, `resources` Это разделяет: - graph selection build variant-а; - artifact selection внутри уже выбранного variant-а. ### slot contributions и DSL `slot('')` описывает artifact representation не как один файл или одну задачу, а как набор contributions, которые потом materialize-ятся в отдельный `ArtifactAssembly`. Текущий DSL поддерживает два вида contributions: - topology-aware: - `fromVariant { output(...) }` - `fromRole('') { output(...) }` - `fromLayer('') { output(...) }` - direct: - `from(someFileOrProviderOrTaskOutput)` Смысл DSL по слоям: - `fromVariant/fromRole/fromLayer` выбирают область topology model, в которой contribution активен; - `output(...)` выбирает named output соответствующего `GenericSourceSet`; - `from(Object)` добавляет direct contribution, не зависящий от `variantSources` bindings; - итоговый contribution при materialization: - проверяет, подходит ли текущий `SourceSetUsageBinding`; - выдает object для `files.from(...)`; - при необходимости выдает `BindingKey`, если такой contribution должен схлопываться по logical identity. Связь slot-а с остальной моделью: - `variants` задает topology variant/role/layer; - `variantSources` превращает topology в concrete `SourceSetUsageBinding`; - `variantArtifacts.slot(...)` описывает, какие bindings надо включить в slot; - `VariantArtifactsResolver` превращает contributions slot-а в `FileCollection`; - `VariantArtifactsPlugin` регистрирует для slot-а отдельный `ArtifactAssembly`; - `OutgoingVariantPublication` и `OutgoingArtifactSlotPublication` публикуют уже собранные slot artifacts наружу. Каждый slot materialize-ится в отдельный `ArtifactAssembly`: - task: `process`; - output dir: `build/variant-artifacts//`. ### primary slot Primary slot задает artifact, который публикуется как основной artifact configuration `Elements`. Формы DSL: ```groovy variant('browser') { primarySlot('typesPackage') slot('typesPackage') { fromVariant { output('types') } } } ``` или sugar: ```groovy variant('browser') { primarySlot('typesPackage') { fromVariant { output('types') } } } ``` Правила: - если slot один, он считается primary неявно; - если slots несколько, `primarySlot(...)` обязателен; - `primarySlot` должен ссылаться на существующий slot. ## LIFECYCLE - `VariantsArtifactsPlugin` ждет `variants.whenFinalized(...)`; - после этого валидирует `variantArtifacts`; - регистрирует `ArtifactAssembly` по каждому slot; - materialize-ит outgoing publications; - вызывает `whenOutgoingVariant(...)`; - callbacks replayable. После finalize мутации `variantArtifacts` запрещены. ## EVENTS ### whenOutgoingVariant Replayable callback на готовую outgoing publication variant-а. Подходит для: - настройки общих attributes build variant-а один раз; - настройки per-slot artifact attributes; - доконфигурации `ArtifactAssembly`. ## PAYLOAD TYPES ### OutgoingVariantPublication Содержит: - `variantName()`; - `topologyVariant()`; - `variantArtifact()`; - `configuration()` — primary `Elements`; - `primarySlot()`; - `slots()` — все slot publications; - `secondarySlots()`; - `findSlot(name)`, `requireSlot(name)`. Sugar: - `configureConfiguration(Action|Closure)`. ### OutgoingArtifactSlotPublication Содержит: - `slotName()`; - `primary()`; - `slot()` — модель `VariantArtifactSlot`; - `assembly()`. Sugar: - `configureAssembly(Action|Closure)`; - `configureArtifactAttributes(Action|Closure)`. `configureArtifactAttributes(...)` пишет attributes: - в `Configuration.attributes` для primary slot; - в `ConfigurationVariant.attributes` для secondary slot. ## CONSUMER SIDE ### primary resolution Обычное inter-project resolution выбирает primary artifact `Elements`. Пример: ```groovy configurations { compileView { canBeResolved = true canBeConsumed = false canBeDeclared = true attributes { attribute(variantAttr, 'browser') attribute(slotAttr, 'typesPackage') } } } dependencies { compileView project(':producer') } ``` ### artifact selection for secondary slots Secondary artifacts выбираются через `artifactView`. ```groovy def jsFiles = configurations.compileView.incoming.artifactView { attributes { attribute(slotAttr, 'js') } }.files ``` Здесь graph variant уже выбран, а `artifactView` выбирает нужный secondary artifact representation. ## VALIDATION Проверяется: - variant существует в topology model; - slot contributions не ссылаются на неизвестные role/layer; - при нескольких slots указан `primarySlot`; - `primarySlot` ссылается на существующий slot. ## API ### VariantArtifactsExtension - `variant(String)` — получить/создать variant artifact model; - `variant(String, Action|Closure)` — сконфигурировать variant artifact; - `getVariants()` — контейнер variant artifacts; - `findVariant(name)`, `requireVariant(name)`; - `whenOutgoingVariant(...)`. ### VariantArtifact - `slot(String)` — получить/создать slot; - `slot(String, Action|Closure)` — сконфигурировать slot; - `primarySlot(String)` — назначить primary slot; - `primarySlot(String, Action|Closure)` — sugar: configure slot + mark as primary; - `getSlots()`; - `findSlot(name)`, `requireSlot(name)`; - `findPrimarySlotName()`, `requirePrimarySlotName()`; - `findPrimarySlot()`, `requirePrimarySlot()`. ### VariantArtifactSlot - `from(Object)`; - `fromVariant(...)`; - `fromRole(String, ...)`; - `fromLayer(String, ...)`. Внутренняя модель: - slot хранит contributions, а не строковые rules; - `fromVariant/fromRole/fromLayer` создают topology-aware contributions; - `from(Object)` создает direct contribution, который materialize-ится даже если у variant-а нет ни одного `SourceSetUsageBinding`; - slot отдельно хранит topology references для validation: `referencedRoleNames()` и `referencedLayerNames()`. ### OutputSelectionSpec - `output(name)`; - `output(name, extra...)`. `OutputSelectionSpec` это внутренний DSL-buffer для одного блока `fromVariant/fromRole/fromLayer`. Он локально накапливает contributions и передает их в slot только после успешного завершения configure-блока. ## KEY CLASSES - `VariantsArtifactsPlugin` — plugin adapter и materialization outgoing variants. - `VariantArtifactsExtension` — root DSL и lifecycle. - `VariantArtifact` — outgoing build variant model. - `VariantArtifactSlot` — artifact representation slot. - `VariantArtifactsResolver` — adapter между `variantSources` bindings и contribution model slot-а. - `OutgoingVariantPublication` — payload variant-level publication callback. - `OutgoingArtifactSlotPublication` — payload per-slot publication callback. - `ArtifactAssembly` — assembled files for a slot. ## NOTES - `common` не навязывает доменную логику выбора primary slot. - `common` не фиксирует значения `usage`, `libraryelements` и прочих slot-specific attributes. - `common` не смешивает эту модель с отдельными publish осями вроде package metadata. - Closure callbacks используют delegate-first; для вложенных closure удобнее явный параметр (`publication -> ...`, `slotPublication -> ...`).