##// END OF EJS Templates
Rework variant artifacts materialization model...
Rework variant artifacts materialization model Refactor VariantArtifactsPlugin around a live outgoing artifacts context and split artifact publication into explicit internal services: outgoing variant registry, assembly binding, materialization policy hooks, primary-slot convention, and slot assembly handling. Introduce variant artifact slots as identity-first public API and expose materialized assembly handles through ArtifactAssemblies. Add replayable configuration hooks for outgoing configurations, outgoing slots, outgoing variants, and registered assemblies. Create consumable outgoing configurations per variant, bind the primary slot to the root outgoing artifact set, and publish non-primary slots as Gradle outgoing configuration variants. Add deterministic injective task names for slot assembly tasks, use Sync for directory assembly, and configure the default assembly output location under build/variant-assemblies. Make primary-slot selection finalize-on-read and provide a single-slot convention that fails when no unique default can be inferred. Mark artifact internal implementation package as non-public API.

File last commit:

r35:389e9d6c7860 default
r51:9db7822cd26c default
Show More
variant-artifacts-plugin.md
354 lines | 10.5 KiB | text/x-minidsrc | MarkdownLexer
/ common / variant-artifacts-plugin.md

Variant Artifacts Plugin

NAME

VariantsArtifactsPlugin и extension variantArtifacts.

SYNOPSIS

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:

variant('browser') {
    primarySlot('typesPackage')

    slot('typesPackage') {
        fromVariant { output('types') }
    }
}

или sugar:

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.

Пример:

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.

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 -> ...).