Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions TICKETS.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,11 +172,11 @@ External multimodule OSS validation of meta-build axioms (structure over configu
type-owned plugins, attrs-only call sites, closed matrix). **Not** another in-repo sample.

Open coding: **F-115** (`in_progress`, **`priority: now`**, **cron may continue**). **F-094** Portal remains `blocked` (human).
4h workers: pick F-115 phase C remainder (Proto DataStore / flavors) while gated; if no ticket has `priority: now` / `cron may continue`, respond `[SILENT]` (audit 2026-07-29 gate — interactive “Go ahead” alone is not enough).
4h workers: pick F-115 phase C remainder (full designsystem) while gated; if no ticket has `priority: now` / `cron may continue`, respond `[SILENT]` (audit 2026-07-29 gate — interactive “Go ahead” alone is not enough).

| ID | Status | Title | Notes |
|----|--------|-------|-------|
| F-115 | in_progress · **priority: now** · **cron may continue** | Dogfood Now in Android under Forma | Primary OSS target [`android/nowinandroid`](https://github.com/android/nowinandroid). Design: [`docs/DOGFOOD-NIA.md`](docs/DOGFOOD-NIA.md). **Phase A+B+C through Proto DataStore done:** Hilt Path A + Room Path B + Firebase Path A + Preferences→**Proto** DataStore + ForYou/Bookmarks/Search/Settings + WorkManager sync + **`core-datastore-proto-library`** (`protobufLibrary` Path B). Findings F16–F26. **Next phase C:** flavors / full designsystem. No `androidLibrary`. |
| F-115 | in_progress · **priority: now** · **cron may continue** | Dogfood Now in Android under Forma | Primary OSS target [`android/nowinandroid`](https://github.com/android/nowinandroid). Design: [`docs/DOGFOOD-NIA.md`](docs/DOGFOOD-NIA.md). **Phase A+B+C through binary flavors done:** Hilt Path A + Room Path B + Firebase Path A + Proto DataStore + ForYou/Bookmarks/Search/Settings + WorkManager + **`FormaProductFlavor` demo/prod** on binary. Findings F16–F27. **Next phase C:** full designsystem. No `androidLibrary`. |

## Backlog (lower priority / historical GitHub)

Expand Down
44 changes: 41 additions & 3 deletions docs/CALL-SITE-SURFACE.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ All return **`Unit`**.
| `api` | `api` | `packageName`, `dependencies` | JVM contracts; no Android UI |
| `impl` | `impl` | deps, **`viewBinding`**, **`compose`**, test runners, `buildConfiguration` | Feature impl; no → other `impl` |
| `androidApp` | `app` | deps, **`compose`**, `buildConfiguration`, … | Composition **library** (not APK) — **no** `versionCode`/`Name` |
| `androidBinary` | `binary` | **`versionCode`/`versionName` (required)**, deps, **`compose`**, **`signingConfigs` / `buildTypeSigning`**, … | APK composition root; version + signing are **per-binary only** (F-092 / F-097) |
| `androidBinary` | `binary` | **`versionCode`/`versionName` (required)**, deps, **`compose`**, **`signingConfigs` / `buildTypeSigning`**, **`productFlavors`**, … | APK composition root; version + signing + flavors are **per-binary only** (F-092 / F-097 / F-115) |
| `library` | `library` | deps (pure JVM inside Android plugin) | Distinct from removed `androidLibrary` |
| `util` | `util` | deps | JVM helpers |
| `androidUtil` | `android-util` | deps, **`compose`** | Android helpers, no res content |
Expand Down Expand Up @@ -147,8 +147,46 @@ env or `gradle.properties` (local, gitignored) — never from the repo. `assembl
must stay green without secrets (omit `buildTypeSigning` for `debug` or leave AGP
default).

**Not in scope:** product flavors, Play App Signing backend integration, library
AAR signing APIs.
**Not in scope:** Play App Signing backend integration, library AAR signing APIs.

## Product flavors (F-115 / NiA F7)

| Concern | Owner | Notes |
|---------|--------|--------|
| Flavor dimensions + product flavors | **`androidBinary` call site only** (`productFlavors: List<FormaProductFlavor>`) | Wired to AGP `ApplicationExtension.flavorDimensions` + `productFlavors` |
| Build types | **`BuildConfiguration`** (unchanged) | Flavors are **not** stuffed into build-type map |
| Library / `impl` / `androidApp` flavor API | **None (v1)** | Unflavored libraries resolve against flavored APKs; multi-module `demoImplementation` is a documented gap |
| Raw `android { productFlavors { … } }` | **Rejected as happy path** | Escape hatch only |

**Call-site shape (NiA `demo` / `prod`):**

```kotlin
import tools.forma.android.utils.FormaProductFlavor

androidBinary(
packageName = "com.example.app",
versionCode = 1,
versionName = "0.1.0",
productFlavors = listOf(
FormaProductFlavor(
name = "demo",
dimension = "contentType",
applicationIdSuffix = ".demo",
),
FormaProductFlavor(
name = "prod",
dimension = "contentType",
),
),
dependencies = deps(/* … */),
)
// Tasks: assembleDemoDebug / assembleProdDebug (not bare assembleDebug)
```

**Empty list (default)** keeps a single unflavored APK so existing samples stay
`assembleDebug`. Optional attrs on [FormaProductFlavor]: `versionNameSuffix`,
`matchingFallbacks`, `manifestPlaceholders`, `buildConfigFields` (BuildConfig
fields apply only when project-global `buildFeatures.buildConfig` is already on).

## AGP BuildFeatures (F-091 / GH #88)

Expand Down
22 changes: 19 additions & 3 deletions docs/DOGFOOD-NIA.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Dogfood: Now in Android → Forma (F-115)

**Status:** `in_progress` — phase C Hilt + Room + Firebase + **Proto DataStore** + features + Settings + WorkManager green (flavors / full designsystem next)
**Status:** `in_progress` — phase C through **binary product flavors** green (full designsystem next)
**Upstream:** [android/nowinandroid](https://github.com/android/nowinandroid) (Apache-2.0)
**Pinned checkout (local):** `/Users/claw/work/nowinandroid` @ `7d45eae` (main tip when cloned 2026-07-29)
**Dogfood fork:** `/Users/claw/work/nowinandroid-forma`
Expand Down Expand Up @@ -156,7 +156,8 @@ These are the **value** of dogfooding — expected friction, not blockers to ign
### F7 — Product flavors `demo` / `prod`

- Affects source sets and dependency variants (`prodImplementation` FCM on sync).
- **Gap:** document whether Forma `buildConfiguration` covers flavor dimensions or temporary raw AGP remains behind binary type only.
- **Resolved (binary v1, 2026-07-31):** `FormaProductFlavor` + `androidBinary(productFlavors=…)` — binary-only; `BuildConfiguration` stays build-types. NiA spike: `demo`/`prod` on `contentType`; `assembleDemoDebug` / `assembleProdDebug`.
- **Still gap (F27):** library-level productFlavors + `demoImplementation`/`prodImplementation` edges (e.g. sync FCM) — not in v1; unflavored libraries resolve against flavored APK.

## Phased execution plan

Expand Down Expand Up @@ -339,7 +340,21 @@ Workspace: same **`forma-spike`** external tree.
| **F26** | Generated proto types expose lite supers → consumers need `protobuf-kotlin-lite` on compile classpath |
| Verify | `forma-spike` `./gradlew :binary:assembleDebug` → **BUILD SUCCESSFUL** (493 tasks); APK ~14 MB |

**Still open in phase C:** flavors, full designsystem port.
**Still open in phase C (pre-flavors):** full designsystem port.

### Phase C — Binary product flavors ✅ (2026-07-31)

| Step | Result |
|------|--------|
| Engine | `FormaProductFlavor` + pure `resolveProductFlavorPlan` + `ApplicationExtension.applyProductFlavors`; binary-only attr on `androidBinary` / feature config |
| Keep separate | `BuildConfiguration` = build-types only (not flavors) |
| Spike binary | `hiltFirebaseBinary(productFlavors = demo+prod contentType)`; demo `applicationIdSuffix = ".demo"` |
| Tasks | `:binary:assembleDemoDebug` + `:binary:assembleProdDebug` (bare `assembleDebug` gone once flavored) |
| **F7** | closed for APK root |
| **F27** | multi-module library flavors / `prodImplementation` FCM still deferred — unflavored libs OK against flavored app |
| Verify | `forma-spike` assembleDemoDebug + assembleProdDebug green after `publish-local 0.1.3-NIA` |

**Still open in phase C:** full designsystem / core.ui port.

### Phase D — Case study write-up

Expand Down Expand Up @@ -374,6 +389,7 @@ Workspace: same **`forma-spike`** external tree.
| F24 | 2026-07-31 | `deps()` overload mix | Cannot pass `.ksp` NamedDependency and `target()` FormaTarget in one `deps(...)` call (distinct overloads). Compose `transitiveDeps + deps(ksp) + deps(target)`. |
| F25 | 2026-07-31 | Protobuf needs type-owned apply on JVM library | NiA `datastore-proto` is pure JVM + `com.google.protobuf`. Engine needed `libraryTarget` (like `androidUtilTarget`) so Path B `protobufLibrary` can pass derived type. Call sites stay attrs-only. |
| F26 | 2026-07-31 | Proto lite supers on consumers | `UserPreferences` / enums extend protobuf lite; androidUtil consumer must `transitiveDeps(protobuf-kotlin-lite)` even when proto module already depends on it (project dep does not re-export non-api runtime the same way as NiA `api(libs.protobuf…)`). |
| F27 | 2026-07-31 | Library product flavors / variant deps | Binary-only `FormaProductFlavor` closes F7 for APK. NiA also flavors libraries + `prodImplementation(FCM)` on sync. v1 leaves libraries unflavored (AGP resolves). Future: optional library flavor attrs or type-owned missingDimensionStrategy — do **not** put free-form `android { productFlavors }` on every module. |

## Local reference commands

Expand Down
24 changes: 24 additions & 0 deletions docs/PROGRESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,30 @@

Newest entries first.

## 2026-07-31 — F-115: NiA dogfood binary product flavors (F7)

- **Ticket:** F-115 still `in_progress` · `priority: now` · `cron may continue` (full designsystem next)
- **Skills/modes:** Hermes direct dogfood + mechanical binary-only flavor API (F-092/F-097 class; no Grok Build this slice)
- **Engine (forma repo):**
- `FormaProductFlavor` + pure `resolveProductFlavorPlan` / `applyProductFlavors`
- `androidBinary(productFlavors=…)` + `AndroidBinaryFeatureConfiguration.productFlavors`
- `BuildConfiguration` stays build-types only; empty flavors = unflavored default
- Unit tests `FormaProductFlavorTest`; thin wrappers (`hiltBinary`/`hiltFirebaseBinary`/`firebaseBinary`) forward attr
- **External tree:** `/Users/claw/work/nowinandroid-forma/forma-spike`
- binary: demo/prod on `contentType`; demo `applicationIdSuffix=.demo`
- dummy `google-services.json` clients for base + `.demo` packages
- version `0.12.0-nia-forma-flavors`
- **F7** closed for APK root; **F27** library flavors / `prodImplementation` deferred
- **Verify (real host):**
- `plugins/` `./gradlew :android:test jacocoHappyPathCoverageVerification` → **BUILD SUCCESSFUL**
- `bash scripts/publish-local.sh 0.1.3-NIA` → ok
- `forma-spike` `./gradlew :binary:assembleDemoDebug :binary:assembleProdDebug` → **BUILD SUCCESSFUL** (538 tasks)
- APKs under `binary/build/outputs/apk/{demo,prod}/debug/`
- **Docs:** `docs/DOGFOOD-NIA.md` phase C flavors + F7/F27; `CALL-SITE-SURFACE` product flavors; TICKETS F-115 notes; spike README
- **Commits/PRs:** this branch → PR base `v2`
- **Blockers:** none product; F-094 Portal still human-blocked
- **Next:** full designsystem / core.ui port

## 2026-07-31 — F-115: NiA dogfood Proto DataStore

- **Ticket:** F-115 still `in_progress` · `priority: now` · `cron may continue` (flavors / full designsystem next)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import tools.forma.android.target.AndroidTargetTypes
import tools.forma.android.target.registerTargetPlugin
import tools.forma.android.target.targetPlugin
import tools.forma.android.utils.BuildConfiguration
import tools.forma.android.utils.FormaProductFlavor
import tools.forma.android.utils.FormaSigningConfig
import tools.forma.android.visibility.Public
import tools.forma.android.visibility.Visibility
Expand Down Expand Up @@ -56,6 +57,7 @@ fun Project.firebaseBinary(
buildConfiguration: BuildConfiguration = BuildConfiguration(),
signingConfigs: Map<String, FormaSigningConfig> = emptyMap(),
buildTypeSigning: Map<String, String> = emptyMap(),
productFlavors: List<FormaProductFlavor> = emptyList(),
) {
FirebasePluginBindings
androidBinary(
Expand All @@ -67,5 +69,6 @@ fun Project.firebaseBinary(
buildConfiguration = buildConfiguration,
signingConfigs = signingConfigs,
buildTypeSigning = buildTypeSigning,
productFlavors = productFlavors,
)
}
9 changes: 9 additions & 0 deletions plugins/android/src/main/java/androidBinary.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import tools.forma.android.feature.applyFeatures
import tools.forma.android.target.AndroidTargetRegistry
import tools.forma.android.target.AndroidTargetTypes
import tools.forma.android.utils.BuildConfiguration
import tools.forma.android.utils.FormaProductFlavor
import tools.forma.android.utils.FormaSigningConfig
import tools.forma.android.validation.disallowResources
import tools.forma.deps.core.FormaDependency
Expand Down Expand Up @@ -39,6 +40,11 @@ import tools.forma.validation.validate
* take a signing API. Do not use raw module-level `android { signingConfigs }` as
* the supported path.
*
* **Product flavors (F-115 / NiA F7):** [productFlavors] are **binary-only** attrs
* ([FormaProductFlavor]). [BuildConfiguration] stays build-types only. Empty list
* (default) keeps a single unflavored APK. v1 does **not** put flavors on library
* targets — multi-module `demoImplementation` edges stay a documented gap.
*
* @param packageName Application package name / `applicationId` + namespace
* @param owner owner of the target, team responsible for maintenance
* @param versionCode Android `versionCode` for this APK (required; per-binary)
Expand All @@ -47,6 +53,7 @@ import tools.forma.validation.validate
* @param buildConfiguration Android Gradle Plugin build-type configuration DSL
* @param signingConfigs named [FormaSigningConfig] entries for this APK (optional)
* @param buildTypeSigning map of build type name → signing config name (optional)
* @param productFlavors product flavors for this APK (optional; empty = unflavored)
* @param testInstrumentationRunner class name used for instrumentation tests execution
* @param consumerMinificationFiles Proguard/R8 minification files list
* @param manifestPlaceholders placeholders to be injected in manifest
Expand All @@ -60,6 +67,7 @@ fun Project.androidBinary(
buildConfiguration: BuildConfiguration = BuildConfiguration(),
signingConfigs: Map<String, FormaSigningConfig> = emptyMap(),
buildTypeSigning: Map<String, String> = emptyMap(),
productFlavors: List<FormaProductFlavor> = emptyList(),
testInstrumentationRunner: String = androidJunitRunner,
consumerMinificationFiles: Set<String> = emptySet(),
manifestPlaceholders: Map<String, Any> = emptyMap(),
Expand All @@ -83,6 +91,7 @@ fun Project.androidBinary(
manifestPlaceholders = manifestPlaceholders,
signingConfigs = signingConfigs,
buildTypeSigning = buildTypeSigning,
productFlavors = productFlavors,
selfValidator = selfV,
compose = compose,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ package tools.forma.android.feature
import com.android.build.api.dsl.ApplicationExtension
import tools.forma.android.target.BinaryTargetTemplate
import tools.forma.android.utils.BuildConfiguration
import tools.forma.android.utils.FormaProductFlavor
import tools.forma.android.utils.FormaSigningConfig
import tools.forma.android.utils.applyBuildTypeSigning
import tools.forma.android.utils.applyCoreLibraryDesugaring
import tools.forma.android.utils.applyFrom
import tools.forma.android.utils.applyProductFlavors
import tools.forma.android.utils.applySigningConfigs
import tools.forma.validation.Validator
import tools.forma.validation.validator
Expand All @@ -21,6 +23,9 @@ import tools.forma.validation.validator
*
* [signingConfigs] / [buildTypeSigning] are **per-binary** (F-097 / GH #51) —
* APK signing identity lives only on the composition root, not on library shells.
*
* [productFlavors] are **per-binary** (F-115 / NiA F7) — flavor dimensions live
* on the APK root only in v1; [BuildConfiguration] stays build-types only.
*/
data class AndroidBinaryFeatureConfiguration(
val packageName: String,
Expand All @@ -37,6 +42,11 @@ data class AndroidBinaryFeatureConfiguration(
* so call sites do not need AGP `SigningConfig` references inside build-type lambdas.
*/
val buildTypeSigning: Map<String, String> = emptyMap(),
/**
* Product flavors for this APK (optional). Empty = unflavored binary (default).
* See [FormaProductFlavor].
*/
val productFlavors: List<FormaProductFlavor> = emptyList(),
/** Enable Jetpack Compose for the application (APK) target. */
val compose: Boolean = false,
val selfValidator: Validator = validator(BinaryTargetTemplate)
Expand Down Expand Up @@ -73,6 +83,8 @@ fun androidBinaryFeatureDefinition(
signingConfigs = signingConfigs,
buildTypeSigning = configuration.buildTypeSigning,
)
// Product flavors after build types (binary-only; empty = unflavored).
applyProductFlavors(configuration.productFlavors)
compileOptions.applyFrom(formaConfiguration)
applyCoreLibraryDesugaring(project, formaConfiguration)

Expand Down
Loading
Loading