Publish per-module Featured manifest (producer side)#197
Conversation
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>
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
There was a problem hiding this comment.
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 readsflags.txt, maps eachLocalFlagEntryto aFlagDescriptor, and writes JSON via kotlinx-serialization 1.11.0. - New consumable
featuredManifestconfiguration withUsage = "featured-manifest"and a customschema-majorInt 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. |
There was a problem hiding this comment.
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>
What changed
Adds a new consumable Gradle configuration
featuredManifest(Usage =featured-manifest, schema-major = 1) that publishes a per-modulefeatured-manifest.jsondescribing the local and remote feature flags declared viafeatured { }DSL.Each module applying
dev.androidbroadcast.featurednow registers agenerateFeaturedManifest@CacheableTaskthat:flags.txt(produced by the existingresolveFeatureFlagstask) viaScanResultParser.parseLocalFlagEntries();LocalFlagEntryinto a self-containedFlagDescriptor(key, propertyName, kind, valueType, defaultValue, enumTypeFqn, description, category, expiresAt);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 legacyFlagRegistry/GeneratedFlagRegistrarAPI (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 singleGeneratedFeaturedRegistrywith no manual registration.PR A only introduces the producer-side artifact; existing single-module consumers see no change yet.
Design decisions
ConfigParam<...>objects directly from manifest fields — it does not reference per-moduleGeneratedLocalFlags/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).flags.txt. Like the five existingGenerate*Tasks, the new task reads the intermediateflags.txt. Migration to a JSON source happens in one commit in PR C, when the legacy pipeline is removed.dev.androidbroadcast.featured.schema-major = 1lets the aggregator filter incompatible consumers at variant matching time, before any JSON decoding.FeaturedManifestfor the evolvability policy.withVariantsFromConfiguration { skip() }actively breaks KMP publication because thefeaturedManifestconfiguration was never registered with thekotlinMultiplatformcomponent. Custom consumable configurations with arbitraryUsageattributes are not auto-published by Java / KMP / AGP software components — each component only exposes variants added viaaddVariantsFromConfiguration.FeaturedKmpPublicationTestgates this invariant.Artifacts
swarm-report/featured-manifest-pr-a-plan.mdswarm-report/featured-manifest-pr-a-report.mdswarm-report/featured-manifest-pr-a-finalize.mdswarm-report/research/research-flag-registry-autogen.mdHow to test
Local validation:
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 whenANDROID_HOME/ANDROID_SDK_ROOTis not set. KMP smoke test (FeaturedKmpPublicationTest) runs unconditionally.Release Notes
Added
featuredManifestconfiguration, schema v1). Existing flag-generation pipeline is unchanged. Consumer-side aggregation arrives in a follow-up release.Status
swarm-report/featured-manifest-pr-a-plan.md)/check/finalizerequiretest +[libraries]alphabetical order), 5 NITs documented, 3 acknowledged risksAcknowledged risks
FlagContainer._flagscollects without uniqueness check; samekeydeclared twice in DSL silently produces twoFlagDescriptorentries. Producer remains a pass-through by plan design.outgoingVariantsintegration test removed. AGP 9.1.0 throwsConcurrentModificationExceptionwhen iterating Android variants alongside our consumable configuration. Coverage of the configuration setup retained at ProjectBuilder level viaFeaturedManifestConfigurationTest(7 tests).Checklist
:featured-gradle-plugin:check)spotlessCheck)FeaturedKmpPublicationTest)## Unreleased/### Added🤖 Generated with Claude Code