Skip to content

RulesEngine: Maven Central publishing wiring#3488

Draft
ajpallares wants to merge 14 commits into
mainfrom
pallares/rules-engine-distribution
Draft

RulesEngine: Maven Central publishing wiring#3488
ajpallares wants to merge 14 commits into
mainfrom
pallares/rules-engine-distribution

Conversation

@ajpallares

@ajpallares ajpallares commented May 14, 2026

Copy link
Copy Markdown
Member

Resolves SDK-4338

Stacked on top of: #3478 — :rules-engine: internal skeleton module.

Description

This PR flips on every Maven Central publishing piece for the :rules-engine module:

  • Drops the :rules-engine short-circuit in ConfigureConditionalPublishing so com.vanniktech.maven.publish gets applied again.
  • Adds the mavenPublishing { … } block (AndroidSingleVariantLibrary("defaultsRelease", …)), gated by plugins.withId("com.vanniktech.maven.publish") { … } so the custom-entitlement-computation deploy lane keeps working.
  • Adds :rules-engine:metalavaGenerateSignatureDefaultsRelease to scripts/api-dump.sh so CI catches API drift in rules-engine/api.txt.
  • Adds the dokkaHtmlPartial suppression so the published multi-module dokka site doesn't include an empty rules-engine page.
  • Re-adds the api(project(":rules-engine")) constraint in :bom.

The metalava { … } config + the committed rules-engine/api.txt baseline already live in the skeleton PR (#3478) — they're standard hygiene for every revenuecat.public.library module — so this PR just wires them into CI enforcement.

Why a separate PR?

The skeleton itself (#3478) ships a kotlin android library gated by @InternalRulesEngineAPI with no functionality and no consumer. We deliberately split publishing wiring out so the skeleton can land independently and we don't start publishing empty purchases-rules-engine versions to Maven Central on every SDK release (Maven Central versions are immutable, so once 10.7.0 exists we're committed to keeping it published forever).

This PR is kept open as a draft and will be merged alongside (or just before) the first PR that introduces a real consumer of :rules-engine. Until then, the skeleton compiles, runs tests, and gets detekt'd on every PR without producing any external artifact.

ajpallares and others added 9 commits May 14, 2026 10:21
Sets up the plumbing for an internal rules-engine module that the SDK can
depend on without coupling to :purchases or :ui:revenuecatui. Includes:

- New :rules-engine Gradle module using the existing
  `revenuecat-public-library` convention plugin (Metalava, Dokka, Kover,
  Vanniktech publish, baseline profile, explicit-API mode).
- Single-flavor (`apis { defaults }`); no `billingclient` dimension since
  the rules engine has no Billing Client dependency. Publishes a single
  `purchases-rules-engine` artifact instead of a bc7/bc8 split.
- Module-scoped `mavenPublishing.configure(AndroidSingleVariantLibrary("defaultsRelease"))`
  override so the global `ANDROID_VARIANT_TO_PUBLISH=defaultsBc8Release`
  default doesn't apply here.
- Placeholder `RulesEngine` Kotlin object plus a smoke test so CI exercises
  the module from day one. No actual rules logic yet.
- BOM constraint added so consumers using the BOM get an aligned version.

Co-authored-by: Cursor <cursoragent@cursor.com>
Every public declaration in this module is intended to be visible only
to the rest of the SDK (`:purchases`, `:ui:revenuecatui`, hybrid
bridges), not to app developers, so put the opt-in gate in place from
day one instead of bolting it on later.

Sibling annotation, not the existing one
----------------------------------------

`@InternalRevenueCatAPI` lives in `:purchases` and we deliberately
keep `:rules-engine` standalone (no dependency on `:purchases`), so
this defines a parallel `@InternalRulesEngineAPI` in
`com.revenuecat.purchases.rules` with identical
`@RequiresOptIn(level=ERROR)` semantics. Two annotations doing the
same job is mildly redundant but unambiguous in the IDE and avoids
coupling the two modules just for an annotation.

Changes
-------

- New `InternalRulesEngineAPI.kt` mirroring the shape of
  `:purchases`'s `InternalRevenueCatAPI`.
- `RulesEngine` object annotated with `@InternalRulesEngineAPI`.
- Test class opts in with `@OptIn(InternalRulesEngineAPI::class)`.
- Metalava configured (per-module) to add
  `com.revenuecat.purchases.rules.InternalRulesEngineAPI` to the
  hidden-annotations list, on top of the
  `com.revenuecat.purchases.InternalRevenueCatAPI` entry already added
  by the `revenuecat-public-library` convention plugin.
- `api.txt` regenerated: `RulesEngine` is now hidden; the annotation
  itself remains public so consumers can opt in.

Verified
--------

- `./gradlew :rules-engine:testDefaultsDebugUnitTest` ✔
- `./gradlew :rules-engine:metalavaCheckCompatibilityDefaultsRelease` ✔
- `./gradlew detektAll` ✔

Co-authored-by: Cursor <cursoragent@cursor.com>
Drop the explainer comment above the `mavenPublishing.configure(...)`
override in `rules-engine/build.gradle.kts` and the doc on
`InternalRulesEngineAPI`. The behavior is self-evident from the code
and the annotation already carries a `@RequiresOptIn` message.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Gate the `mavenPublishing { ... }` configuration on the
  `com.vanniktech.maven.publish` plugin actually being applied.
  `ConfigureConditionalPublishing` in the convention plugin skips the
  publish plugin when `ANDROID_VARIANT_TO_PUBLISH` contains
  `customEntitlementComputation` (which `:rules-engine` doesn't have),
  so the unconditional block was breaking
  `./gradlew :purchases:publish -PANDROID_VARIANT_TO_PUBLISH=customEntitlementComputationBc8Release`
  — i.e. the "Deploying Custom Entitlements Computation version"
  fastlane step — with `Unresolved reference: mavenPublishing` while
  configuring `:rules-engine`.

- Suppress `:rules-engine` from `dokkaHtmlMultiModule`. The existing
  `HideInternalRevenueCatAPIPlugin` is hardcoded to
  `com.revenuecat.purchases.InternalRevenueCatAPI` and only applied to
  `:purchases`, so without this `RulesEngine` and `InternalRulesEngineAPI`
  would leak into the published docs at `docs/{version}/`. Every public
  symbol in this module is gated by `@InternalRulesEngineAPI`, so a
  dedicated docs page would be empty anyway — suppressing the module
  is simpler than generalizing the hide-plugin.

- Add `:rules-engine:metalavaGenerateSignatureDefaultsRelease` to
  `scripts/api-dump.sh`. The script previously only invoked the
  `Bc8`/`Bc7`/`customEntitlement` task names, so `:rules-engine`
  (single-flavor, no `billingclient` dimension) was never regenerated,
  meaning the committed `rules-engine/api.txt` couldn't act as a
  tripwire via `scripts/api-check.sh`. With this change a leaked
  non-internal API would show up as a diff in CI.

Verified:
- `:purchases:publish --dry-run -PANDROID_VARIANT_TO_PUBLISH=customEntitlementComputationBc8Release` now only fails on the unrelated `mavenCentralUsername`/`Password` credentials error.
- `:rules-engine:dokkaHtmlPartial` reports `Exiting Generation: Nothing to document`.
- `scripts/api-dump.sh` runs the rules-engine task and `api.txt` round-trips clean.
- `:rules-engine:testDefaultsReleaseUnitTest`, `:rules-engine:metalavaCheckCompatibilityDefaultsRelease`, and `detektAll` all pass.

Co-authored-by: Cursor <cursoragent@cursor.com>
The module is currently a skeleton with no functionality and no
consumers, so publishing `purchases-rules-engine` would ship an empty
artifact whose Maven Central version we'd then be on the hook to keep
publishing forever. Defer the publishing wiring until the JSON Logic
engine lands.

- Short-circuit `:rules-engine` in `ConfigureConditionalPublishing` so
  `com.vanniktech.maven.publish` is never applied to it. The module
  still compiles, gets detekt'd, runs tests, and is dokka-suppressed on
  every PR — there's just no AAR pushed to Sonatype.
- Drop the `mavenPublishing { configure(AndroidSingleVariantLibrary(…)) }`
  block (and its imports) from `rules-engine/build.gradle.kts`. With the
  publish plugin no longer applied, it's unreachable.
- Remove `api(project(":rules-engine"))` from `:bom` so consumers
  exploring the BoM don't see a real-looking `purchases-rules-engine`
  they could pull in and get nothing.
- Drop the `:rules-engine:metalavaGenerateSignatureDefaultsRelease`
  entry from `scripts/api-dump.sh`. It will be re-added in the same
  follow-up PR that flips publishing back on, to keep all the "publish
  wiring" in one switch-flip.

Follow-up PR (alongside the first real consumer of `RulesEngine`):
revert the `:rules-engine` short-circuit, restore the
`mavenPublishing { … }` block (gated with `plugins.withId(...)` so it
doesn't break CE deploys), restore the `:bom` entry, and re-add the
api-dump invocation.

Verified:
- `:purchases:publish --dry-run -PANDROID_VARIANT_TO_PUBLISH=customEntitlementComputationBc8Release` only fails on the unrelated `mavenCentralUsername`/`Password` credentials error — `:rules-engine` configures cleanly.
- `:rules-engine:tasks --all` lists no Maven publish tasks (only the unrelated `prepareLintJarForPublish` Android Lint internal).
- `:bom:tasks` resolves without `:rules-engine`.
- `:rules-engine:testDefaultsReleaseUnitTest`, `:rules-engine:metalavaCheckCompatibilityDefaultsRelease`, `:rules-engine:dokkaHtmlPartial`, and `detektAll` all pass.
- `scripts/api-dump.sh` leaves all `api*.txt` files unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
Keep this PR minimal: an internal kotlin android library that
compiles, tests, and gets detekt'd. Everything that's only relevant
when the module ships an artifact lives in a separate draft PR:

- Drop the module-level `metalava { … }` block (and the committed
  `api.txt` baseline). Without a CI step regenerating it, the file
  was just static; we'll re-add both — and wire them into
  `scripts/api-dump.sh` — once publishing flips on.
- Drop the `dokkaHtmlPartial` suppression. The module would only show
  up in `dokkaHtmlMultiModule` output once it's published, so there's
  nothing to hide today.

Replace the two trailing comments with a single short pointer to the
follow-up PR and the `ConfigureConditionalPublishing` short-circuit.

Co-authored-by: Cursor <cursoragent@cursor.com>
Flip on every distribution-related piece for the `:rules-engine`
module so that, once a real consumer of the engine ships, releasing
it is just a matter of merging this PR alongside the skeleton:

- Drop the `:rules-engine` short-circuit from
  `ConfigureConditionalPublishing` so the `com.vanniktech.maven.publish`
  plugin gets applied to the module again.
- Add the `mavenPublishing { … }` block (`AndroidSingleVariantLibrary`
  on `defaultsRelease`) inside a `plugins.withId("com.vanniktech.maven.publish")`
  block so the custom-entitlement-computation deploy lane keeps working.
- Restore the `metalava { … }` configuration (hidden annotations) and
  the committed `rules-engine/api.txt` baseline, and add the matching
  `metalavaGenerateSignatureDefaultsRelease` task to `scripts/api-dump.sh`
  so CI catches API drift.
- Restore the `dokkaHtmlPartial` suppression so the published
  multi-module dokka site doesn't include an empty
  `rules-engine` page.
- Re-add the `api(project(":rules-engine"))` constraint in `:bom`.

This stays a draft until the engine has functionality and a consumer
so we don't push empty `purchases-rules-engine` versions to Maven
Central. Stacked on top of the `pallares/rules-engine-skeleton`
branch.

Co-authored-by: Cursor <cursoragent@cursor.com>
ajpallares and others added 2 commits May 14, 2026 20:27
`metalava { hiddenAnnotations.add(InternalRulesEngineAPI) }` + a
committed `api.txt` are standard hygiene for every module that uses
`revenuecat.public.library`. The CocoaPods/Maven publishing wiring is
the only thing that's truly "exists but unwired" in this PR and that
already moved to its own draft.

Keeping the metalava config here also avoids a duplicate `metalava {}`
block landing in both the distribution PR (which adds the
`api-dump.sh` entry to enforce drift) and the enforce-internal-api PR
(which uses metalava to verify nothing leaks outside
`@InternalRulesEngineAPI`).

The `api.txt` file is not yet regenerated by CI in this PR — that
follow-up lives in the distribution draft PR.

Co-authored-by: Cursor <cursoragent@cursor.com>
@codecov

codecov Bot commented May 14, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 79.89%. Comparing base (aed6e2f) to head (19bdc99).

Additional details and impacted files
@@                       Coverage Diff                       @@
##           pallares/rules-engine-skeleton    #3488   +/-   ##
===============================================================
  Coverage                           79.89%   79.89%           
===============================================================
  Files                                 369      369           
  Lines                               14871    14871           
  Branches                             2048     2048           
===============================================================
  Hits                                11881    11881           
  Misses                               2157     2157           
  Partials                              833      833           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Base automatically changed from pallares/rules-engine-skeleton to main May 28, 2026 15:50
matteinn pushed a commit to matteinn/purchases-android that referenced this pull request Jun 5, 2026
### Checklist
- [x] If applicable, unit tests

### Motivation
Resolves SDK-4336

Adds a `:rules-engine-internal` module so the upcoming JSON Logic engine
can ship as a separate library. Sibling iOS PR:
RevenueCat/purchases-ios#6787.

### Description
- Skeleton module with an internal `RulesEngine` namespace, smoke test,
and Maven publishing deferred until the engine has a consumer
([RevenueCat#3488](RevenueCat#3488)).
- Module and artifact named `rules-engine-internal` /
`purchases-rules-engine-internal` to make it clear that the API is
unstable and not meant to be reached for directly, mirroring iOS'
`RulesEngineInternal`.
- Metalava and Dokka are also skipped for this module since it has no
public-facing API for third-party developers.
- New `test-rules-engine-internal` CircleCI job runs
`:rules-engine-internal:testDefaultsDebugUnitTest`.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Scaffolding-only module and build/CI wiring with no runtime behavior
or public API surface changes to the main SDK.
> 
> **Overview**
> Adds a new **`:rules-engine-internal`** Android library (artifact
`purchases-rules-engine-internal`) as a placeholder for the upcoming
JSON Logic rules engine, aligned with iOS’ internal rules module naming.
> 
> The module only exposes an **`internal` `RulesEngine` namespace** plus
a smoke unit test. **Maven publishing, Metalava, and Dokka are
explicitly skipped** for this path until there is a real consumer; Dokka
application is refactored behind **`configureDokka()`** on the
public-library convention plugin.
> 
> **CircleCI** gains **`test-rules-engine-internal`**
(`:rules-engine-internal:testDefaultsDebugUnitTest`), wired into the
main workflow and the release approval gate.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
2d7bf9d. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant