# HG changeset patch # User cin # Date 2026-04-21 19:27:31 # Node ID e376d0cab00e765da8d960318a8ed4e066b2eb87 # Parent 780370baa54c1055b902596f328af69181b08840 Set the project version to 0.1.0, add publication descriptions/license metadata, and keep module-level docs as compatibility pointers to the root documentation. diff --git a/.hgignore b/.hgignore --- a/.hgignore +++ b/.hgignore @@ -1,6 +1,7 @@ syntax: glob .gradle/ .codex/ +build/ common/build/ common/bin/ variants/build/ diff --git a/LICENSE b/LICENSE new file mode 100644 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +BSD 2-Clause License + +Copyright (c) 2026 gradle-common contributors. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/PUBLISHING.md b/PUBLISHING.md new file mode 100644 --- /dev/null +++ b/PUBLISHING.md @@ -0,0 +1,99 @@ +# Local Ivy Publishing + +This project currently publishes only to a local Ivy repository. Maven Central, +signing, and Gradle Plugin Portal publication are intentionally out of scope for +the current preparation step. + +Published Ivy descriptors include the BSD-2-Clause license metadata. + +## Repository + +The configured Ivy repository is: + +```text +${user.home}/ivy-repo +``` + +This is defined in: + +- `common/build.gradle` +- `variants/build.gradle` + +## Verify Before Publishing + +Run a full clean verification: + +```bash +./gradlew clean check javadoc jar sourcesJar javadocJar --rerun-tasks +``` + +Optional configuration-cache smoke check: + +```bash +./gradlew check --configuration-cache +``` + +## Publish + +Publish all modules: + +```bash +./gradlew publish +``` + +Publish modules explicitly: + +```bash +./gradlew :common:publishIvyPublicationToIvyRepository \ + :variants:publishIvyPublicationToIvyRepository +``` + +Safe smoke publish into a temporary repository: + +```bash +./gradlew -Duser.home=/tmp/gradle-common-ivy-smoke \ + :common:publishIvyPublicationToIvyRepository \ + :variants:publishIvyPublicationToIvyRepository \ + --rerun-tasks +``` + +## Consume Locally + +Use `buildscript` classpath for now: + +```groovy +buildscript { + repositories { + ivy { + url "${System.properties['user.home']}/ivy-repo" + } + mavenCentral() + } + dependencies { + classpath 'org.implab.gradle:variants:0.1.0' + } +} + +apply plugin: 'org.implab.gradle-variants' +apply plugin: 'org.implab.gradle-variants-sources' +``` + +The `plugins {}` DSL needs generated plugin marker artifacts and is not part of +the current local Ivy contract. + +## Published Artifacts + +Each module publishes: + +- `-.jar` +- `--sources.jar` +- `--javadoc.jar` +- `ivy.xml` +- Gradle module metadata + +## Current Coordinates + +```text +org.implab.gradle:common:0.1.0 +org.implab.gradle:variants:0.1.0 +``` diff --git a/README.md b/README.md new file mode 100644 --- /dev/null +++ b/README.md @@ -0,0 +1,296 @@ +# gradle-common + +Java 21 multi-project build with shared Gradle utilities and experimental +variant-oriented Gradle plugins. + +The repository currently publishes to a local Ivy repository only. Maven Central +and Gradle Plugin Portal publication are intentionally not configured yet. + +## Modules + +- `common` - shared Gradle utilities, JSON helpers, shell execution helpers, and + small core value/util classes. +- `variants` - Gradle plugins for variant topology, source-set materialization, + and outgoing artifact slots. + +## Requirements + +- JDK 21. +- Gradle Wrapper from this repository, currently Gradle 8.10.2. + +The produced bytecode targets Java 21. + +## License + +This project is licensed under the BSD 2-Clause "Simplified" License +(`BSD-2-Clause`). See [LICENSE](LICENSE). + +## Local Build + +```bash +./gradlew clean check javadoc jar sourcesJar javadocJar --rerun-tasks +``` + +Configuration cache smoke check: + +```bash +./gradlew check --configuration-cache +``` + +## Local Ivy Publication + +The current publication target is: + +```text +${user.home}/ivy-repo +``` + +Publish both modules locally: + +```bash +./gradlew :common:publishIvyPublicationToIvyRepository \ + :variants:publishIvyPublicationToIvyRepository +``` + +or: + +```bash +./gradlew publish +``` + +The publication includes: + +- main jar +- sources jar +- javadoc jar +- Ivy descriptor +- Gradle module metadata + +## Local Consumption + +Current plugin ids are packaged as classic Gradle plugin marker resources inside +the `variants` jar: + +- `org.implab.gradle-variants` +- `org.implab.gradle-sources` +- `org.implab.gradle-variants-sources` +- `org.implab.gradle-variants-artifacts` + +Until Gradle Plugin Portal marker artifacts are configured, consume the plugin +through `buildscript` classpath: + +```groovy +buildscript { + repositories { + ivy { + url "${System.properties['user.home']}/ivy-repo" + } + mavenCentral() + } + dependencies { + classpath 'org.implab.gradle:variants:0.1.0' + } +} + +apply plugin: 'org.implab.gradle-variants' +apply plugin: 'org.implab.gradle-variants-sources' +``` + +The `plugins { id(...) version(...) }` DSL is not part of the current local Ivy +contract. + +## Variants DSL + +`variants` defines the finalized build topology. It does not create compile +tasks, source directories, or outgoing publications by itself. + +```groovy +apply plugin: 'org.implab.gradle-variants' + +variants.layers.create('main') +variants.layers.create('test') +variants.roles.create('main') +variants.roles.create('test') + +variants.variant('browser') { + role('main') { + layers('main') + } + role('test') { + layers('main', 'test') + } +} + +variants.whenFinalized { view -> + println view.entries.collect { + "${it.variant().name}:${it.role().name}:${it.layer().name}" + }.sort() +} +``` + +The finalized model exposes cheap identity objects: `Variant`, `Role`, `Layer`, +and the normalized relation `(variant, role, layer)`. + +## Sources DSL + +`sources` creates standalone `GenericSourceSet` objects. This is useful for +fallback workflows that do not need variant topology. + +```groovy +apply plugin: 'org.implab.gradle-sources' + +sources.create('main') { + declareOutputs('js') + registerOutput('js', layout.projectDirectory.file('inputs/main.js')) + + sets.create('ts') { + srcDir 'src/main/ts' + } +} +``` + +`SourcesPlugin` applies layout conventions: + +- `sourceSetDir = src/` +- `outputsDir = build/out/` + +The base `GenericSourceSet` model itself is convention-free. + +## Variant Sources DSL + +`variantSources` derives compile units from finalized `variants`. + +A compile unit is: + +```text +(variant, layer) +``` + +Selectors configure materialized compile-unit source sets: + +```groovy +apply plugin: 'org.implab.gradle-variants-sources' + +variants.layers.create('main') +variants.roles.create('main') +variants.variant('browser') { + role('main') { + layers('main') + } +} + +variantSources { + layer('main') { + sourceSet { + declareOutputs('js') + registerOutput('js', layout.projectDirectory.file('inputs/browser.js')) + } + } + + configureEach { + println "sourceSet=${sourceSet.name}, variant=${variant.name}, layer=${layer.name}" + } +} +``` + +Selector order for future materialization is: + +```text +configureEach -> variant -> layer -> unit +``` + +Late selector registration is controlled by: + +```groovy +variantSources { + lateConfigurationPolicy { + failOnLateConfiguration() + } +} +``` + +Compile-unit source set names are generated by default as: + +```text + + capitalize() +``` + +Name collisions fail by default and may be resolved deterministically: + +```groovy +variantSources { + namingPolicy { + resolveNameCollision() + } +} +``` + +## Variant Artifacts DSL + +`variantArtifacts` is an experimental outgoing artifact layer over `variants` +and `variantSources`. + +The current model maps: + +- `Variant` to a variant-level consumable outgoing configuration. +- `Slot` to a Gradle outgoing artifact variant inside that configuration. +- `primarySlot` to the primary artifact set of the outgoing configuration. + +```groovy +apply plugin: 'org.implab.gradle-variants-artifacts' + +variants.layers.create('main') +variants.roles.create('main') +variants.variant('browser') { + role('main') { + layers('main') + } +} + +variantSources.layer('main') { + sourceSet { + declareOutputs('types', 'js') + registerOutput('types', layout.projectDirectory.file('inputs/index.d.ts')) + registerOutput('js', layout.projectDirectory.file('inputs/index.js')) + } +} + +variantArtifacts { + variant('browser') { + primarySlot('typesPackage') { + fromVariant { + output('types') + } + } + slot('js') { + fromVariant { + output('js') + } + } + } + + whenOutgoingConfiguration { publication -> + publication.configuration { + description = "Outgoing contract for ${publication.variant.name}" + } + } + + whenOutgoingSlot { publication -> + println "slot=${publication.artifactSlot.slot.name}, primary=${publication.primary}" + } +} +``` + +The artifact API is still considered pre-1.0 and may change. + +## Publication Status + +Current status: + +- local Ivy publication only +- no Maven Central publication metadata +- no signing +- no Gradle Plugin Portal marker artifacts +- BSD-2-Clause license committed + +Before external publication, see [RELEASE_CHECKLIST.md](RELEASE_CHECKLIST.md). diff --git a/RELEASE_CHECKLIST.md b/RELEASE_CHECKLIST.md new file mode 100644 --- /dev/null +++ b/RELEASE_CHECKLIST.md @@ -0,0 +1,63 @@ +# Release Checklist + +This checklist tracks what should be true before publishing outside the local +Ivy repository. + +## Current Scope + +For now the project is prepared for local Ivy publication only. + +## Required Before External Publication + +- Add Maven publication (`maven-publish`) if publishing to Maven repositories. +- Add signing for Maven Central or any repository that requires signed + artifacts. +- Add complete POM metadata: project name, description, URL, license, + developers, and SCM coordinates. +- Keep Java 21 as the public baseline and document it for consumers. +- Decide whether `variants` artifact APIs are published as experimental or + split into a later module. +- Add Gradle plugin marker artifact generation if `plugins { id(...) version(...) }` + must work. +- Add a published-consumption smoke test that resolves artifacts from a + temporary local repository. + +## Local Ivy Release Steps + +1. Ensure the Mercurial working tree is clean. +2. Run: + + ```bash + ./gradlew clean check javadoc jar sourcesJar javadocJar --rerun-tasks + ``` + +3. Optionally run: + + ```bash + ./gradlew check --configuration-cache + ``` + +4. Publish locally: + + ```bash + ./gradlew publish + ``` + +5. Optionally smoke-publish into `/tmp` first: + + ```bash + ./gradlew -Duser.home=/tmp/gradle-common-ivy-smoke \ + :common:publishIvyPublicationToIvyRepository \ + :variants:publishIvyPublicationToIvyRepository \ + --rerun-tasks + ``` + +6. Verify `${user.home}/ivy-repo/org.implab.gradle` contains `common` and + `variants` for the expected version. + +## API Status + +- `common` is intended to be a shared utility library. +- `variants` core and source APIs are pre-1.0 and should be treated as + evolving. +- `variantArtifacts` is experimental and may change more aggressively. diff --git a/common/build.gradle b/common/build.gradle --- a/common/build.gradle +++ b/common/build.gradle @@ -3,6 +3,8 @@ plugins { id "ivy-publish" } +description = "Shared Gradle build utilities used by Implab plugins" + java { withJavadocJar() withSourcesJar() @@ -46,6 +48,10 @@ publishing { descriptor.description { text = providers.provider({ description }) } + descriptor.license { + name = "BSD-2-Clause" + url = "https://spdx.org/licenses/BSD-2-Clause.html" + } } } } diff --git a/common/readme.md b/common/readme.md --- a/common/readme.md +++ b/common/readme.md @@ -1,134 +1,18 @@ -# Gradle Common Sources Model - -## NAME - -`gradle-common/common` — набор плагинов для моделирования вариантов сборки, -регистрации source sets и интеграции этой модели с toolchain-адаптерами. - -## SYNOPSIS - -```groovy -plugins { - id 'org.implab.gradle-variants-sources' -} - -variants { - layer('mainBase') - layer('mainAmd') - - variant('browser') { - role('main') { layers('mainBase', 'mainAmd') } - } -} - -variantSources { - bind('mainBase') { - configureSourceSet { - declareOutputs('compiled') - } - } - - bind('mainAmd').sourceSetNamePattern = '{variant}{layerCap}' +# Gradle Common - whenRegistered { sourceSetName() } - - whenBound { ctx -> - ctx.configureSourceSet { - declareOutputs('typings') - } - } -} -``` - -## DESCRIPTION - -Модуль состоит из трех логических частей: +`common` is the shared utility module used by the Gradle plugins in this +repository. -- `variants` — декларативная доменная модель сборки; -- `sources` — модель физически регистрируемых source sets; -- `variantSources` — адаптер, который связывает первые две модели. - -Ниже раскрытие каждой части. - -### variants - -`variants` задает структуру пространства сборки: какие есть слои, какие роли -используют эти слои в каждом варианте, какие есть атрибуты и artifact slots. -Модель не создает задачи и не привязана к TS/JS. - -Практический смысл: - -- формализовать архитектуру сборки; -- дать адаптерам единый источник правды. - -### sources +It contains: -`sources` описывает независимые source sets (`GenericSourceSet`) с именованными -outputs. Это уже "физический" уровень, к которому удобно привязывать задачи, -артефакты и task inputs/outputs. - -Практический смысл: - -- создать единый контракт по входам/выходам; -- регистрировать результаты задач как outputs source set; -- минимизировать ручные `dependsOn` за счет модели outputs. - -### variantSources - -`variantSources` регистрирует source sets на основе `variants`, применяет -конфигурацию layer-bindings и отдает события (`whenRegistered`, `whenBound`) для -адаптеров других плагинов. - -Практический смысл: - -- переводить логическую модель `variants` в executable-модель `sources`; -- навешивать политики toolchain на зарегистрированные source sets; -- синхронизировать плагины через replayable callback-контракт. - -## DOMAIN MODEL - -- `BuildLayer` — canonical identity-model объявленного слоя. -- `BuildVariant` — агрегат ролей, атрибутов, артефактных слотов. -- `BuildRole` — роль внутри варианта, содержит ссылки на declared layer names. -- `GenericSourceSet` — зарегистрированный набор исходников и outputs. -- `LayerBindingSpec` — публичный DSL-contract adapter policy/callbacks для слоя. -- `SourceSetRegistration` — payload события регистрации source set. -- `SourceSetUsageBinding` — payload события usage-binding. - -## EVENT CONTRACT +- core Gradle helper utilities +- small language/value helpers +- shell execution helpers +- JSON DSL and JSON-writing task support -- `whenRegistered`: - - событие нового уникального source set name; - - replayable. -- `whenBound`: - - событие каждой usage-связки `variant/role/layer`; - - replayable. - -Closure callbacks работают в delegate-first режиме (`@DelegatesTo`). Для -вложенных closure рекомендуется явный параметр (`ctx -> ...`). - -## KEY CLASSES +It no longer contains the variant/source/artifact plugin model. Those plugins +live in the `variants` module. -- `SourcesPlugin` — регистрирует extension `sources`. -- `GenericSourceSet` — модель источников/outputs для конкретного имени. -- `VariantsPlugin` — регистрирует extension `variants` и lifecycle finalize. -- `BuildVariantsExtension` — корневой API модели вариантов. -- `BuildVariant` — API ролей, attributes и artifact slots варианта. -- `VariantsSourcesPlugin` — применяет `variants` + `sources` и запускает адаптер. -- `VariantSourcesExtension` — API bind/events registration. -- `LayerBindingSpec` — слой-конкретный DSL для policy/configuration source set. -- `SourceSetRegistration` — payload `whenRegistered(...)`. -- `SourceSetUsageBinding` — payload `whenBound(...)`. +See the root [README.md](../README.md) and [PUBLISHING.md](../PUBLISHING.md) for +current build and local Ivy publication instructions. -## NOTES - -- Marker ids: - - `org.implab.gradle-variants` - - `org.implab.gradle-variants-sources` -- `SourcesPlugin` пока class-only (без marker id). - -## SEE ALSO - -- `sources-plugin.md` -- `variants-plugin.md` -- `variant-sources-plugin.md` diff --git a/common/sources-plugin.md b/common/sources-plugin.md --- a/common/sources-plugin.md +++ b/common/sources-plugin.md @@ -1,83 +1,7 @@ -# Sources Plugin - -## NAME - -`SourcesPlugin` и extension `sources`. - -## SYNOPSIS - -```groovy -// Обычно подключается транзитивно через org.implab.gradle-variants-sources - -sources { - register('main') { - declareOutputs('compiled', 'typings') - - sets { - ts { srcDir 'src/main/ts' } - js { srcDir 'src/main/js' } - } - } -} -``` - -## DESCRIPTION - -`SourcesPlugin` регистрирует extension `sources` типа -`NamedDomainObjectContainer`. - -`GenericSourceSet` — это автономный source bundle с четким контрактом outputs. - -### sourceSetDir - -Базовый каталог набора. Конвенция по умолчанию: `src/`. - -### outputsDir - -Базовый каталог результатов набора. Конвенция по умолчанию: `build/`. - -### sets +# Moved: Sources Plugin -Контейнер `SourceDirectorySet` внутри `GenericSourceSet`. Используется для -логического разделения подпапок (например `ts`, `js`, `typings`). - -### outputs contract - -Outputs именованные и должны быть объявлены заранее: - -- `declareOutputs(...)` — декларация доступных output keys; -- `output(name)` — доступ к `ConfigurableFileCollection` для output key; -- `registerOutput(...)` — регистрация output из файлов или task provider. - -Такой контракт упрощает wiring задач через inputs/outputs без ручного -`dependsOn`. - -## API - -### SourcesPlugin - -- `apply(Project)` — добавляет extension `sources` в проект. -- `getSourcesExtension(Project)` — возвращает контейнер `GenericSourceSet`. +The `SourcesPlugin` implementation is now part of the `variants` module, not +the `common` module. -### GenericSourceSet +Current documentation is maintained in the root [README.md](../README.md). -- `getSourceSetDir()` — root directory источников набора. -- `getOutputsDir()` — root directory результатов набора. -- `getSets()` — контейнер поднаборов `SourceDirectorySet`. -- `declareOutputs(...)` — объявляет разрешенные output names. -- `output(name)` — возвращает `FileCollection` для конкретного output. -- `registerOutput(name, files...)` — добавляет файлы в output. -- `registerOutput(name, task, mapper)` — связывает output с task provider. -- `getAllOutputs()` — агрегированный `FileCollection` всех outputs. -- `getAllSourceDirectories()` — агрегированный `FileCollection` всех source dirs. - -## KEY CLASSES - -- `SourcesPlugin` — регистрация extension `sources`. -- `GenericSourceSet` — модель источников и outputs. - -## NOTES - -- Обращение к `output(name)` без предварительного `declareOutputs(name)` - приводит к ошибке валидации. -- Плагин `sources` сейчас без marker id. diff --git a/common/variant-artifacts-plugin.md b/common/variant-artifacts-plugin.md --- a/common/variant-artifacts-plugin.md +++ b/common/variant-artifacts-plugin.md @@ -1,354 +1,8 @@ -# 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. +# Moved: Variant Artifacts Plugin -## 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 +The `VariantArtifactsPlugin` implementation is now part of the `variants` +module, not the `common` module. -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 +Current documentation is maintained in the root [README.md](../README.md) and +[variant_artifacts.md](../variant_artifacts.md). -- `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 -> ...`). diff --git a/common/variant-sources-plugin.md b/common/variant-sources-plugin.md --- a/common/variant-sources-plugin.md +++ b/common/variant-sources-plugin.md @@ -1,159 +1,9 @@ -# Variant Sources Plugin - -## NAME - -`VariantsSourcesPlugin` и extension `variantSources`. - -## SYNOPSIS - -```groovy -plugins { - id 'org.implab.gradle-variants-sources' -} - -variants { - layer('main') - - variant('browser') { - role('main') { layers('main') } - } - - variant('node') { - role('main') { layers('main') } - } -} - -variantSources { - bind('main').sourceSetNamePattern = '{layer}' - - bind('main') { - configureSourceSet { - declareOutputs('compiled') - } - } - - whenRegistered { sourceSetName() } - whenBound('browser') { roleName() } -} -``` +# Moved: Variant Sources Plugin -## DESCRIPTION - -`VariantsSourcesPlugin` применяет `VariantsPlugin` и `SourcesPlugin`, затем -регистрирует source sets из модели `variants`. - -Точка запуска registration: - -- `variants.whenFinalized(model -> registerSourceSets(...))` - -### registration - -Для каждой usage-связки `variant/role/layer` вычисляется имя source set, -регистрируется `GenericSourceSet` (если он еще не существует), затем -вызываются callbacks. - -### binding - -`bind('')` возвращает `LayerBindingSpec` и задает policy для этого -слоя: - -- как именовать source set; -- как конфигурировать source set; -- какие callbacks вызвать на registration/binding. - -### sourceSetNamePattern - -`sourceSetNamePattern` определяет naming policy зарегистрированного source set. - -Default: - -- `{variant}{layerCap}` - -Tokens: - -- `{variant}`, `{variantCap}` -- `{role}`, `{roleCap}` -- `{layer}`, `{layerCap}` - -Имя санитизируется (`[^A-Za-z0-9_.-] -> _`). +The `VariantSourcesPlugin` implementation is now part of the `variants` module, +not the `common` module. -Ограничение: - -- один `sourceSetName` не может быть порожден разными слоями. - -## EVENTS - -### whenRegistered - -- callback на новый уникальный source set; -- replayable; -- при shared source set срабатывает один раз. - -### whenBound - -- callback на каждую usage-связку `variant/role/layer`; -- replayable; -- подходит для per-usage логики. - -### variant filter - -Фильтр по варианту поддерживает только usage-binding: - -- `whenBound(String variantName, ...)` - -## PAYLOAD TYPES - -`SourceSetRegistration` содержит: - -- `layerName`, `sourceSetName`; -- `sourceSet` (`NamedDomainObjectProvider`). - -Sugar: - -- `configureSourceSet(Action|Closure)`. - -`SourceSetUsageBinding` содержит: - -- `variantName`, `roleName`, `layerName`, `sourceSetName`; -- `sourceSet` (`NamedDomainObjectProvider`). +Current documentation is maintained in the root [README.md](../README.md), +[variant_sources.md](../variant_sources.md), and +[variant_sources_precedence.md](../variant_sources_precedence.md). -Sugar: - -- `configureSourceSet(Action|Closure)`. - -## API - -### VariantSourcesExtension - -- `bind(BuildLayer)` — получить/создать binding для canonical layer identity. -- `bind(String)` — получить/создать binding по имени слоя. -- `bind(String, Action|Closure)` — сконфигурировать binding. -- `bind(BuildLayer, Action|Closure)` — сконфигурировать binding по `BuildLayer`. -- `getBindings()` — read-only snapshot текущих bindings. -- `whenRegistered(...)` — глобальные callbacks регистрации source set. -- `whenBound(...)` — глобальные callbacks usage-binding. -- `whenBound(String variantName, ...)` — usage-binding callbacks с variant-filter. - -### LayerBindingSpec - -- `sourceSetNamePattern` — naming policy для source set слоя. -- `configureSourceSet(...)` — слойная конфигурация `GenericSourceSet`. -- `whenRegistered(...)` — callbacks регистрации в рамках слоя. -- `whenBound(...)` — callbacks usage-binding в рамках слоя. - -## KEY CLASSES - -- `VariantsSourcesPlugin` — точка входа plugin adapter. -- `VariantSourcesExtension` — глобальный DSL bind/events. -- `LayerBindingSpec` — публичный DSL-contract layer-local policy/callbacks. -- `SourceSetRegistration` — payload регистрации source set. -- `SourceSetUsageBinding` — payload usage-binding. - -## NOTES - -- `sourceSetNamePattern` фиксируется при первом чтении в registration - (`finalizeValueOnRead`). -- runtime state bindings скрыт внутри adapter implementation (`LayerBinding`). -- name-based bindings резолвятся к canonical `BuildLayer` через registry `variants`. -- Closure callbacks используют delegate-first. -- Для вложенных closure лучше явный параметр (`ctx -> ...`). diff --git a/common/variants-plugin.md b/common/variants-plugin.md --- a/common/variants-plugin.md +++ b/common/variants-plugin.md @@ -1,112 +1,8 @@ -# Variants Plugin - -## NAME - -`VariantsPlugin` и extension `variants`. - -## SYNOPSIS - -```groovy -plugins { - id 'org.implab.gradle-variants' -} - -variants { - layer('mainBase') - layer('mainAmd') - - variant('browser') { - attributes { - string('jsRuntime', 'browser') - string('jsModule', 'amd') - } - - role('main') { - layers('mainBase', 'mainAmd') - } - - artifactSlot('mainCompiled') - } -} -``` - -## DESCRIPTION - -`VariantsPlugin` задает доменную модель сборки и ее валидацию. Плагин не -регистрирует compile/copy/bundle задачи напрямую. - -### layers - -Глобальные логические слои. Служат единым словарем имен, на которые затем -ссылаются роли. - -### variants - -Именованные варианты исполнения/пакетирования (`browser`, `node`, и т.д.). -Вариант агрегирует роли, атрибуты и artifact slots. - -### roles - -Роль описывает набор слоев в пределах варианта (`main`, `test`, `tools`). -Одна роль может ссылаться на несколько слоев. - -### attributes +# Moved: Variants Plugin -Typed-атрибуты (`Attribute -> Provider`) для передачи параметров в -адаптеры и публикацию артефактов. - -### artifact slots - -Именованные слоты ожидаемых артефактов варианта. Используются как контракт -между моделью варианта и плагинами, создающими/публикующими результаты. - -## VALIDATION - -В `finalizeModel()` выполняется проверка: - -- роль не может ссылаться на неизвестный layer; -- пустые имена layer запрещены; -- имена ролей в варианте должны быть уникальны; -- имена artifact slots в варианте должны быть уникальны. - -## LIFECYCLE - -- `VariantsPlugin` вызывает `variants.finalizeModel()` на `afterEvaluate`. -- после `finalizeModel()` мутации модели запрещены. -- `whenFinalized(...)` replayable. - -## API - -### BuildVariantsExtension +The `VariantsPlugin` implementation is now part of the `variants` module, not +the `common` module. -- `layer(...)` — объявление или конфигурация `BuildLayer`. -- `variant(...)` — объявление или конфигурация `BuildVariant`. -- `layers { layer(...) }`, `variants { ... }` — grouped DSL без публикации container API наружу. -- `all(...)` — callback для всех вариантов. -- `findLayer(name)`, `requireLayer(name)`, `getAllLayers()` — доступ к registry слоев. -- `getAll()`, `find(name)`, `require(name)` — доступ к вариантам. -- `validate()` — явный запуск валидации. -- `finalizeModel()` — валидация + финализация модели. -- `whenFinalized(...)` — callback по завершенной модели (replayable). - -### BuildVariant - -- `attributes { ... }` — атрибуты варианта (+ sugar `string/bool/integer`). -- `role(...)`, `roles { ... }` — роли варианта. -- `artifactSlot(...)`, `artifactSlots { ... }` — артефактные слоты. +Current documentation is maintained in the root [README.md](../README.md) and +the design notes in [variant_sources.md](../variant_sources.md). -## KEY CLASSES - -- `VariantsPlugin` — точка входа плагина. -- `BuildVariantsExtension` — root extension и lifecycle. -- `BuildVariant` — агрегатная модель варианта. -- `BuildLayer` — canonical identity-model слоя. -- `BuildRole` — модель роли. -- `BuildArtifactSlot` — модель артефактного слота. -- `VariantAttributes` — typed wrapper для variant attributes. - -## NOTES - -- Модель `variants` intentionally agnostic к toolchain. -- Layer registry хранится как явная identity-map, а не как публичный `NamedDomainObjectContainer`. -- Интеграция с задачами выполняется через `variantSources` и адаптеры. diff --git a/gradle.properties b/gradle.properties --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ group=org.implab.gradle -version=1.0 \ No newline at end of file +version=0.1.0 diff --git a/variant_artifacts.md b/variant_artifacts.md --- a/variant_artifacts.md +++ b/variant_artifacts.md @@ -1,5 +1,9 @@ # Variants and Variant Artifacts +> Design note. The current user-facing DSL and local publication workflow are +> documented in [README.md](README.md). Some snippets below are conceptual and +> describe model direction rather than exact public API. + ## Overview This document describes the artifact model built on top of `variants` and @@ -592,7 +596,7 @@ model rather than define it. Examples: -- `whenOutgoingVariant(...)` +- `whenOutgoingConfiguration(...)` - `whenOutgoingSlot(...)` These hooks are adapter-facing customization points over already declared diff --git a/variant_sources.md b/variant_sources.md --- a/variant_sources.md +++ b/variant_sources.md @@ -1,5 +1,9 @@ # Variants and Variant Sources +> Design note. The current user-facing DSL and local publication workflow are +> documented in [README.md](README.md). Some snippets below are conceptual and +> describe model direction rather than exact public API. + ## Overview This document describes a two-layer model for build variants: @@ -510,7 +514,8 @@ This policy is intentionally modeled as property: * it must be chosen before the first selector rule is added -* selector rules here mean `variant(...)`, `layer(...)`, and `unit(...)` +* selector rules here mean `configureEach(...)`, `variant(...)`, `layer(...)`, + and `unit(...)` * once chosen, it cannot be changed later * it controls runtime behavior, not just a stored value * the enforcement point is the first selector registration itself, not variants @@ -519,7 +524,7 @@ property: For source sets configured before materialization, selector precedence remains: ```text -variant < layer < unit +configureEach < variant < layer < unit ``` For already materialized source sets in `warn` and `allow` modes: diff --git a/variant_sources_precedence.md b/variant_sources_precedence.md --- a/variant_sources_precedence.md +++ b/variant_sources_precedence.md @@ -16,14 +16,36 @@ Instead, it provides configuration selec ## Selectors -Three selectors are available: +Four selectors are available: +- `configureEach(...)` - `variant(...)` - `layer(...)` - `unit(...)` They all target the same set of compile units, but at different levels of specificity. +### `configureEach(...)` + +`configureEach(...)` applies configuration to every materialized compile-unit +source set. + +Example: + +```groovy +variantSources { + configureEach { + sourceSet { + declareOutputs("js") + } + } +} +``` + +Use this selector for global source-set conventions. + +--- + ### `variant(...)` `variant(...)` applies configuration to all compile units that belong to the given variant. @@ -33,7 +55,9 @@ Example: ```groovy variantSources { variant("browser") { - declareOutputs("js", "dts") + sourceSet { + declareOutputs("js", "dts") + } } } ``` @@ -57,8 +81,10 @@ Example: ```groovy variantSources { layer("main") { - set("ts") { - srcDir("src/main/ts") + sourceSet { + sets.create("ts") { + srcDir("src/main/ts") + } } } } @@ -83,8 +109,10 @@ Example: ```groovy variantSources { unit("browser", "main") { - set("resources") { - srcDir("src/browserMain/resources") + sourceSet { + sets.create("resources") { + srcDir("src/browserMain/resources") + } } } } @@ -103,14 +131,15 @@ Use this selector for the most specific For each compile unit, source-set configuration is applied in the following order: ```text -variant < layer < unit +configureEach < variant < layer < unit ``` This means: -1. `variant(...)` actions are applied first -2. `layer(...)` actions are applied next -3. `unit(...)` actions are applied last +1. `configureEach(...)` actions are applied first +2. `variant(...)` actions are applied next +3. `layer(...)` actions are applied next +4. `unit(...)` actions are applied last Each next level is allowed to refine or override the previous one. @@ -160,7 +189,8 @@ Available modes: Policy rules: - the policy must be chosen before the first selector rule is added -- selector rules here mean `variant(...)`, `layer(...)`, and `unit(...)` +- selector rules here mean `configureEach(...)`, `variant(...)`, `layer(...)`, + and `unit(...)` - once chosen, the policy cannot be changed later - the policy is single-valued; it is not intended to be switched during further configuration @@ -173,8 +203,8 @@ Operationally: already materialized targets - `warn` and `allow` keep compatibility with imperative late mutation - in `warn` and `allow`, already materialized targets observe the late action in - actual registration order, not in reconstructed `variant < layer < unit` - order + actual registration order, not in reconstructed + `configureEach < variant < layer < unit` order --- @@ -234,11 +264,11 @@ Naming policy is fixed when the finalize Operationally this means: -- policy selection must happen before `variantSources.whenFinalized(...)` +- policy selection must happen before `variantSources.whenAvailable(...)` becomes observable - compile-unit names are projected and validated before queued - `whenFinalized(...)` callbacks are replayed -- changing naming policy from inside a `whenFinalized(...)` callback is too late + `whenAvailable(...)` callbacks are replayed +- changing naming policy from inside a `whenAvailable(...)` callback is too late --- @@ -246,19 +276,31 @@ Operationally this means: ```groovy variantSources { + configureEach { + sourceSet { + declareOutputs("js", "dts") + } + } + variant("browser") { - declareOutputs("js", "dts") + sourceSet { + registerOutput("js", layout.projectDirectory.file("inputs/browser.js")) + } } layer("main") { - set("ts") { - srcDir("src/main/ts") + sourceSet { + sets.create("ts") { + srcDir("src/main/ts") + } } } unit("browser", "main") { - set("resources") { - srcDir("src/browserMain/resources") + sourceSet { + sets.create("resources") { + srcDir("src/browserMain/resources") + } } } } @@ -270,6 +312,9 @@ 1. `variant("browser")` 2. `layer("main")` 3. `unit("browser", "main")` +The global `configureEach(...)` selector is applied before the listed +variant/layer/unit selectors for every compile unit. + For compile unit `(browser, rjs)` the effective configuration is built in this order: 1. `variant("browser")` @@ -324,7 +369,7 @@ This guarantees that: - precedence is: ```text -variant < layer < unit +configureEach < variant < layer < unit ``` - registration order is preserved within the same selector level diff --git a/variants/build.gradle b/variants/build.gradle --- a/variants/build.gradle +++ b/variants/build.gradle @@ -3,6 +3,8 @@ plugins { id "ivy-publish" } +description = "Variant, source-set, and outgoing artifact model plugins for Gradle builds" + java { withJavadocJar() withSourcesJar() @@ -15,9 +17,7 @@ dependencies { compileOnly libs.jdt.annotations api gradleApi(), - libs.bundles.jackson - - implementation project(":common") + project(":common") testImplementation gradleTestKit() testImplementation "org.junit.jupiter:junit-jupiter-api:5.11.4" @@ -52,6 +52,10 @@ publishing { descriptor.description { text = providers.provider({ description }) } + descriptor.license { + name = "BSD-2-Clause" + url = "https://spdx.org/licenses/BSD-2-Clause.html" + } } } }