# 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('<name>')` публикуется один outgoing
build variant:

- primary configuration `<variant>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('<name>')` описывает artifact representation не как один файл или одну
задачу, а как набор contributions, которые потом materialize-ятся в отдельный
`ArtifactAssembly`.

Текущий DSL поддерживает два вида contributions:

- topology-aware:
  - `fromVariant { output(...) }`
  - `fromRole('<role>') { output(...) }`
  - `fromLayer('<layer>') { 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<Variant><Slot>`;
- output dir: `build/variant-artifacts/<variant>/<slot>`.

### primary slot

Primary slot задает artifact, который публикуется как основной artifact
configuration `<variant>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 `<variant>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 `<variant>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 -> ...`).
