Skip to content

Publish per-module Featured manifest (producer side)#197

Merged
kirich1409 merged 2 commits into
developfrom
feat/per-module-featured-manifest
May 19, 2026
Merged

Publish per-module Featured manifest (producer side)#197
kirich1409 merged 2 commits into
developfrom
feat/per-module-featured-manifest

Conversation

@kirich1409
Copy link
Copy Markdown
Contributor

What changed

Adds a new consumable Gradle configuration featuredManifest (Usage = featured-manifest, schema-major = 1) that publishes a per-module featured-manifest.json describing the local and remote feature flags declared via featured { } DSL.

Each module applying dev.androidbroadcast.featured now registers a generateFeaturedManifest @CacheableTask that:

  • reads flags.txt (produced by the existing resolveFeatureFlags task) via ScanResultParser.parseLocalFlagEntries();
  • maps each LocalFlagEntry into a self-contained FlagDescriptor (key, propertyName, kind, valueType, defaultValue, enumTypeFqn, description, category, expiresAt);
  • serialises the result as JSON via kotlinx-serialization (1.11.0, schema v1).

The output file is exposed as the consumable configuration's outgoing artifact through lazy provider wiring (artifacts.add(name, manifestTask.flatMap { it.outputFile }) { builtBy(manifestTask) }).

This is the producer side of the multi-module aggregation redesign. Follow-up PRs add: the aggregator (GeneratedFeaturedRegistry) that resolves manifests via dependency resolution (PR B); removal of the legacy FlagRegistry / GeneratedFlagRegistrar API (PR C); and a multi-module sample exercising the full chain (PR D).

The existing five flag-generation tasks (generateConfigParam, generateFlagRegistrar, generateFeaturedProguardRules, generateIosConstVal, generateXcconfig) are unchanged.

Why

Today consumers must wire GeneratedFlagRegistrar.register(flagRegistry) by hand in every entry point. In a multi-module setup this both clutters app code and requires direct dependency on every feature module to surface its flags. The redesign moves the wiring to a Gradle-native aggregation: each feature module declares its flags through a consumable artifact, the app module resolves them via standard Gradle dependency resolution, and the aggregator generates a single GeneratedFeaturedRegistry with no manual registration.

PR A only introduces the producer-side artifact; existing single-module consumers see no change yet.

Design decisions

  • Manifest is self-contained. Aggregator (PR B) constructs ConfigParam<...> objects directly from manifest fields — it does not reference per-module GeneratedLocalFlags / GeneratedRemoteFlags. These currently live in a hardcoded package (dev.androidbroadcast.featured.generated) and would collide across modules. The manifest carries everything aggregator needs (key, propertyName, kind, valueType, defaultValue, enumTypeFqn).
  • Source of truth: flags.txt. Like the five existing Generate*Tasks, the new task reads the intermediate flags.txt. Migration to a JSON source happens in one commit in PR C, when the legacy pipeline is removed.
  • Schema versioning via Gradle attribute. Custom attribute dev.androidbroadcast.featured.schema-major = 1 lets the aggregator filter incompatible consumers at variant matching time, before any JSON decoding.
  • Public contract is the JSON wire format, not the Kotlin types. Consumer (PR B) may implement its own deserialization model independently; renaming internal Kotlin fields does not break the contract. See KDoc on FeaturedManifest for the evolvability policy.
  • Maven-publish guard intentionally omitted. Verified that withVariantsFromConfiguration { skip() } actively breaks KMP publication because the featuredManifest configuration was never registered with the kotlinMultiplatform component. Custom consumable configurations with arbitrary Usage attributes are not auto-published by Java / KMP / AGP software components — each component only exposes variants added via addVariantsFromConfiguration. FeaturedKmpPublicationTest gates this invariant.

Artifacts

  • Plan: swarm-report/featured-manifest-pr-a-plan.md
  • Implementation report: swarm-report/featured-manifest-pr-a-report.md
  • Finalize round summary: swarm-report/featured-manifest-pr-a-finalize.md
  • Research (full redesign context): swarm-report/research/research-flag-registry-autogen.md

How to test

Local validation:

./gradlew :featured-gradle-plugin:check spotlessCheck

Verifies: 210 unit + TestKit tests pass (185 existing + 25 new across 7 test classes), spotless clean, configuration cache stores + reuses without warnings.

Integration test suite (FeaturedManifestIntegrationTest) skips automatically when ANDROID_HOME / ANDROID_SDK_ROOT is not set. KMP smoke test (FeaturedKmpPublicationTest) runs unconditionally.

Release Notes

Added

  • Featured library plugin now publishes a per-module feature-flag manifest as a consumable Gradle artifact (featuredManifest configuration, schema v1). Existing flag-generation pipeline is unchanged. Consumer-side aggregation arrives in a follow-up release.

Status

Stage Result
Plan PASS (swarm-report/featured-manifest-pr-a-plan.md)
Implementation PASS — 1610 LoC added across 27 files (1 main code, 1 plugin edit, 1 version-catalog edit, 1 build-gradle edit, 1 CHANGELOG edit, 7 test classes, 1 test helper, 3 fixtures, 12 fixture support files)
/check PASS — build, lint, 210 tests, spotless
/finalize PASS (Round 1) — 2 BLOCKs fixed (missing require test + [libraries] alphabetical order), 5 NITs documented, 3 acknowledged risks

Acknowledged risks

  • Duplicate-key detection deferred to PR B aggregator. FlagContainer._flags collects without uniqueness check; same key declared twice in DSL silently produces two FlagDescriptor entries. Producer remains a pass-through by plan design.
  • outgoingVariants integration test removed. AGP 9.1.0 throws ConcurrentModificationException when iterating Android variants alongside our consumable configuration. Coverage of the configuration setup retained at ProjectBuilder level via FeaturedManifestConfigurationTest (7 tests).
  • Maven-publish guard removed. Replaced by structural invariant + KMP smoke gate; see Design decisions above.

Checklist

  • Code compiled (:featured-gradle-plugin:check)
  • Spotless clean (spotlessCheck)
  • Tests pass (210 / 210)
  • Regression on existing tests verified
  • KMP publication smoke gate passes (FeaturedKmpPublicationTest)
  • CHANGELOG entry under ## Unreleased / ### Added
  • CI passes on draft PR

🤖 Generated with Claude Code

Adds a new consumable Gradle configuration `featuredManifest` (schema v1,
Usage = "featured-manifest") that publishes a per-module
`featured-manifest.json` description of the module's local and remote
feature flags. Each `dev.androidbroadcast.featured` plugin application now
registers a `generateFeaturedManifest` @CacheableTask that maps
LocalFlagEntry rows from `flags.txt` into a self-contained FlagDescriptor
list (key, propertyName, kind, valueType, defaultValue, enumTypeFqn) and
serialises the result via kotlinx-serialization.

The manifest is the producer side of the multi-module aggregation
redesign: a follow-up PR introduces the aggregator that resolves all
manifests through normal dependency resolution and generates the
GeneratedFeaturedRegistry. The existing five flag-generation tasks are
unchanged. A separate Maven-publish guard is intentionally omitted —
custom consumable configurations are not auto-published by the Java /
KMP / AGP software components, and a KMP smoke fixture gates the
invariant that no `featured-manifest` variant appears in the published
.module metadata.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@kirich1409 kirich1409 added enhancement New feature or request gradle-plugin featured-gradle-plugin work labels May 18, 2026
@kirich1409 kirich1409 marked this pull request as ready for review May 18, 2026 18:18
Copilot AI review requested due to automatic review settings May 18, 2026 18:18
@qodo-code-review
Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces the producer-side of a multi-module aggregation redesign for the Featured Gradle plugin. Each module applying dev.androidbroadcast.featured now generates a per-module featured-manifest.json and exposes it via a new consumable Gradle configuration featuredManifest (Usage = featured-manifest, attribute schema-major = 1). The existing single-module flag-generation pipeline is unchanged; this artifact will be consumed by a future aggregator (PR B).

Changes:

  • New GenerateFeaturedManifestTask (@CacheableTask) that reads flags.txt, maps each LocalFlagEntry to a FlagDescriptor, and writes JSON via kotlinx-serialization 1.11.0.
  • New consumable featuredManifest configuration with Usage = "featured-manifest" and a custom schema-major Int attribute, wired as an outgoing artifact via lazy provider.
  • Schema types (FeaturedManifest, FlagDescriptor, FlagKind, ValueType) with documented wire-format contract; large test suite (unit + TestKit + KMP smoke + integration) and three fixtures.

Reviewed changes

Copilot reviewed 26 out of 27 changed files in this pull request and generated no comments.

Show a summary per file
File Description
gradle/libs.versions.toml Adds kotlinx-serialization 1.11.0 version + lib + plugin coordinates.
featured-gradle-plugin/build.gradle.kts Applies kotlinSerialization plugin and adds runtime dep on kotlinx-serialization-json.
featured-gradle-plugin/src/main/kotlin/.../FeaturedPlugin.kt Registers manifest task and consumable featuredManifest configuration.
.../manifest/GenerateFeaturedManifestTask.kt New cacheable task plus LocalFlagEntry → FlagDescriptor mapper.
.../manifest/FeaturedManifest.kt Schema data classes, SCHEMA_VERSION, configured Json instance with wire-format KDoc.
.../manifest/FeaturedManifestContract.kt Shared constants and schemaMajorAttr Gradle attribute.
featured-gradle-plugin/src/test/.../manifest/*.kt (7 files) Unit, configuration, mapping, serialization, registration, empty-DSL, integration, and KMP-publish tests.
featured-gradle-plugin/src/test/fixtures/* Three TestKit fixture projects (Android library, JVM empty, KMP publish).
CHANGELOG.md Adds Unreleased entry describing the new artifact.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 27 files

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

cubic-dev-ai PR review (cf33824) flagged that
FeaturedManifestIntegrationTest writes Android SDK path directly into
local.properties via File.absolutePath, which on Windows yields raw
backslashes — Java's .properties parser treats backslash as an escape
character and would corrupt the path. Switch to File.invariantSeparatorsPath,
which uniformly emits forward slashes.

An identical pattern exists in the pre-existing FeaturedPluginIntegrationTest;
that file is out of PR A scope and will be aligned in a separate cleanup PR.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@kirich1409 kirich1409 merged commit 8054eef into develop May 19, 2026
9 checks passed
@kirich1409 kirich1409 deleted the feat/per-module-featured-manifest branch May 19, 2026 05:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request gradle-plugin featured-gradle-plugin work

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants