Skip to content

Proposal: Gradle-backed Maven dependency resolution for .NET for Android #12495

Description

@Redth

Summary

.NET for Android should move toward resolving Java dependencies once, at the final application graph boundary, using Gradle as the resolution authority. Binding projects and NuGet packages should publish declarative Maven requirements and artifact provenance instead of independently embedding or downloading their own copies of AAR/JAR files.

The proposed direction is:

  1. Preserve the existing AndroidMavenLibrary and AndroidGradleProject behaviors for compatibility.
  2. Add a separate declarative contract, tentatively named AndroidMavenDependency, for dependencies that flow transitively from projects and NuGet packages to an app.
  3. Aggregate all declarations into one normalized request graph per app and target framework.
  4. Run a small SDK-owned Gradle build, using an SDK-owned binary plugin, Android-compatible variant attributes, and a pinned Gradle distribution.
  5. Export a machine-readable graph plus separate compile and runtime AAR/JAR artifact views, then feed those files into the appropriate existing binding and application pipelines.
  6. Publish dependency manifests through NuGet buildTransitive assets and an equivalent recursive project-reference contract.
  7. Reconcile Gradle-selected components with legacy embedded/downloaded payloads before the current filename-based duplicate checks.
  8. Roll out first in report-only, dual-payload mode. Do not remove AAR/JAR files from existing packages until resolution, locking, offline behavior, and mixed-mode suppression have proven reliable.

NuGet packages must not contribute arbitrary Gradle script fragments. Package inputs should be declarative data only. An application could opt into an app-owned Gradle plugin or script as an explicit full-trust escape hatch.

This would not replace NuGet restore. NuGet remains responsible for managed dependencies; the Android SDK would own a second, post-NuGet Java dependency resolution phase.

Motivation

Existing pieces stop short of app-wide resolution

.NET 9 added useful primitives:

  • AndroidMavenLibrary downloads one exact Maven artifact and its POM, then uses Java Dependency Verification to check that dependencies were fulfilled elsewhere.
  • AndroidGradleProject invokes a project's Gradle Wrapper, builds an AAR or APK, and injects AAR outputs into AndroidLibrary.
  • JavaArtifact metadata and artifact=g:a:v NuGet tags identify Java components supplied by a project or package.
  • AndroidIgnoredJavaDependency, POM parent/import handling, Maven version range parsing, and a reusable Maven cache already exist.

These solve acquisition and validation inside one binding project, but they do not resolve the combined application graph.

Native Library Interop exposes the missing boundary

CommunityToolkit/Maui.NativeLibraryInterop now delegates Android builds to AndroidGradleProject. A binding project builds and binds its wrapper AAR, but AGP does not bundle that module's Maven dependencies into the AAR. The consuming app must repeat those dependencies manually as AndroidMavenLibrary and PackageReference items.

This creates two independent graphs:

  • Gradle's compile-time graph for the wrapper module.
  • The .NET app's NuGet/AAR/JAR graph for runtime packaging.

Nothing guarantees that the selected AndroidX, Kotlin, Material, or vendor SDK versions match.

Existing issues demonstrate related failure modes:

Small proof of concept

A disposable resolver using the repository's Gradle 9.5 wrapper, Gradle's base plugin, and one resolvable configuration requested:

  • androidx.appcompat:appcompat:1.6.1
  • androidx.appcompat:appcompat:1.7.0
  • com.squareup.okio:okio-jvm:3.9.0

Gradle selected appcompat:1.7.0, honored AndroidX alignment metadata, produced 29 AARs plus 14 JARs, and reported both request origins and conflict selection through dependencyInsight. A second offline invocation completed from cache.

This proves that resolving AAR/JAR payloads does not inherently require applying AGP. It does not prove every Android variant-selection case: a production resolver must set AGP-equivalent attributes such as Android JVM environment, Kotlin Android platform, build type, usage, and artifact type. The synthetic build need not apply AGP, but it does need an SDK-owned Android attribute schema and compatibility/disambiguation rules.

Goals

  • Resolve one coherent Java component graph for the final Android app.
  • Preserve Maven POM and Gradle Module Metadata semantics rather than reimplementing a subset in C#.
  • Let app projects, project references, transitive NuGet packages, and AndroidGradleProject modules contribute requirements.
  • Support AARs, JARs, BOMs/platforms, rich versions, exclusions, custom repositories, and authenticated repositories.
  • Make every selected component traceable to the project or NuGet package that requested it.
  • Detect and explain version conflicts before D8/R8 or runtime failures.
  • Provide deterministic, locked, checksum-verified, offline-capable builds.
  • Preserve the existing ecosystem throughout a staged migration.
  • Remove the dependency duplication currently required by Native Library Interop.
  • Avoid invoking Gradle on no-op incremental builds.

Non-goals

  • Replacing NuGet restore or making Maven dependencies participate in NuGet's solver.
  • Translating Maven versions into NuGet versions.
  • Automatically generating or validating managed binding APIs.
  • Allowing arbitrary Gradle code from restored NuGet packages.
  • Silently inferring Maven coordinates for unknown/shaded binaries and changing behavior based on that inference.
  • Guaranteeing binary compatibility between different versions of the same Java component.
  • Merging independent customer Gradle builds, wrappers, settings files, or AGP versions into one multi-project build.

Architecture

SDK-owned synthetic resolver build

The SDK would generate a normalized request file and invoke a constant, SDK-owned Gradle build. An SDK-owned binary settings/project plugin from the local workload would:

  • Configure approved repositories.
  • Create distinct compile and runtime resolution configurations.
  • Apply Android JVM, Kotlin Android, build-type, usage, category, library-elements, and artifact-type attributes.
  • Apply dependencies, constraints, platforms, exclusions, capabilities, and app overrides.
  • Resolve with Gradle's public ResolutionResult, ArtifactCollection, and ArtifactView APIs.
  • Copy exact selected AAR/JAR files into deterministic compile/runtime output directories.
  • Write stable JSON containing request paths, selected versions, selection reasons, repositories, checksums, local-provider decisions, and unresolved edges.

Dependency data should live in sorted JSON, not generated executable Kotlin/Groovy statements. Generated scripts should remain nearly constant.

Why not extend the current C# POM verifier into a solver?

Java.Interop.Tools.Maven remains useful for POM parsing, diagnostics, and compatibility, but should not become the graph authority:

  • Gradle Module Metadata contains variants, constraints, capabilities, and alignment rules not representable in POM.
  • Repository authentication, content filtering, metadata source selection, BOMs, changing modules, and conflict selection would all need to be recreated.
  • Maven, NuGet, and Gradle have different conflict semantics.
  • The current verifier intentionally validates a direct artifact rather than selecting a closure.

Why not import every contributed Gradle file?

Independent Gradle files cannot be safely concatenated or applied into a synthetic build:

  • Build/settings scripts execute arbitrary code during configuration.
  • Script plugins are discouraged for production Gradle logic.
  • Multiple fragments can mutate repositories, configurations, credentials, and resolution timing in incompatible/order-dependent ways.
  • Existing native projects can require different Gradle, AGP, Kotlin, plugin, and JDK versions.
  • Gradle has no sandbox for untrusted scripts.

Application-owned binary plugins or scripts can remain an explicit full-trust escape hatch, but restored packages should contribute data only.

Proposed MSBuild model

Names are provisional and should go through API review.

Preserve AndroidMavenLibrary

Keep its current direct acquisition/binding/packing behavior. It remains valuable for binding-project authoring and compatibility. During pack, the SDK could translate it into a published Maven dependency/provision manifest.

Add AndroidMavenDependency

<ItemGroup>
  <AndroidMavenDependency
      Include="androidx.core:core"
      Version="1.13.1"
      Repository="Google" />
</ItemGroup>

Suggested metadata:

Metadata Meaning
Version Gradle require; participates in normal conflict resolution.
VersionStrictly Exact version/range; incompatible resolution fails.
VersionPrefer Soft preference.
VersionReject Rejected versions/ranges.
Repository Stable repository ID.
Scope Runtime by default; Compile/CompileOnly for binding classpaths.
Exclude Path-local group:artifact exclusions.
Optional Do not add unless requested elsewhere.
PrivateAssets Prevent requirement flow where appropriate.
Reason Human-readable origin for dependency insight.

Versions remain in Maven/Gradle version space and must never be parsed as NuGetVersion.

Add constraints, platforms, repositories, and app overrides

<AndroidMavenConstraint
    Include="org.jetbrains.kotlin:kotlin-stdlib"
    Version="[1.9,2.0)" />

<AndroidMavenPlatform
    Include="com.google.firebase:firebase-bom"
    Version="34.2.0"
    Repository="Google" />

<AndroidMavenRepository
    Include="Mapbox"
    Url="https://api.mapbox.com/downloads/v2/releases/maven"
    IncludeGroup="com.mapbox.*"
    Authentication="Header"
    CredentialIdentity="MapboxDownloads" />

Applications also need explicit final-graph escape hatches such as:

  • AndroidMavenVersionOverride
  • AndroidMavenGlobalExclude
  • AndroidMavenSubstitution
  • AndroidMavenCapabilitySelection

Using an override should be visible in the graph and diagnostics.

Version and compatibility semantics

Use Gradle's normal rule: select the highest version satisfying active requirements, constraints, rejects, and strict bounds. Do not add a "compatible major" heuristic; Android libraries do not uniformly follow semantic versioning.

A managed binding was generated against a concrete Java binary. Its manifest should record:

  • Exact boundAgainst coordinate, version, and checksum.
  • A normal require constraint unless the package author chooses strictly.
  • A package-author-declared tested compatibility range, when known.
  • POM/GMM runtime dependency constraints.

If Gradle selects a version other than boundAgainst, resolve mode should fail unless the package declared that selection compatible or the app supplies an explicit unsafe override. This produces a binding-compatibility diagnostic rather than overloading Gradle's strict-version conflict message.

Dynamic and changing/SNAPSHOT versions should be rejected by default because they undermine MSBuild incrementality and lock-file reproducibility.

Path-local exclusions should preserve Gradle semantics. App-owned global exclusions should warn when removing a component another dependency declared required.

Capabilities/substitutions should cover curated replacement and relocation cases such as old support libraries versus AndroidX, Kotlin stdlib consolidation, renamed artifacts, and placeholder compatibility packages.

Package and project propagation

Versioned manifest

A package/project/file sidecar should distinguish requirements from local providers:

{
  "schemaVersion": 1,
  "producer": {
    "kind": "package",
    "id": "Xamarin.AndroidX.Activity",
    "version": "1.13.0.1",
    "targetFramework": "net11.0-android"
  },
  "dependencies": [
    {
      "coordinate": "androidx.activity:activity",
      "require": "1.13.0",
      "boundAgainst": "1.13.0",
      "compatibleWith": "[1.13.0]",
      "repository": "Google",
      "scope": "Runtime",
      "role": "BoundPrimary"
    }
  ],
  "constraints": [],
  "platforms": [],
  "repositories": [],
  "provides": [
    {
      "coordinate": "androidx.activity:activity:1.13.0",
      "path": "aar/androidx.activity.activity.aar",
      "sha256": "...",
      "payloadKind": "aar",
      "modified": false
    }
  ]
}
  • dependencies: what the consumer needs.
  • provides: what the package already carries locally.
  • constraints: requirements that do not add a component.
  • platforms: BOMs.
  • repositories: repository requirements without secrets.

NuGet

Pack the JSON manifest plus a conventionally named buildTransitive/<tfm>/<PackageId>.props, or import the data fragment from an existing <PackageId>.props/.targets. NuGet only auto-imports conventionally named package files.

The props should only contribute an AndroidMavenManifest item and be guarded by an SDK capability property so older SDKs continue using packaged payloads.

Continue parsing artifact=/artifact_versioned= package tags as a fallback for already published packages, but do not use tags as the long-term contract.

Project references

Add a recursive target contract such as GetAndroidMavenManifests, modeled after GetCopyToOutputDirectoryItems, preserving exact project origins and defining behavior for ReferenceOutputAssembly=false, private references, and references that become package references during pack.

File references

Optionally discover Binding.dll.android-maven.json beside a file-referenced binding assembly. Unknown file references remain on the legacy path.

Integration with AndroidGradleProject

Do not merge native projects into the synthetic resolver build. Instead, extend the Gradle integration so the selected module exports its requested and resolved dependency graph alongside AAR/APK output.

For a com.android.library module, export:

  • Direct requested runtime dependencies and constraints.
  • Resolved runtime components and selected versions.
  • Configuration/build variant.
  • Selection reasons.
  • Artifact checksums where available.

The module AAR remains the direct Bind=true artifact. External dependencies become Bind=false, Pack=false Maven requirements. During dotnet pack, the exported graph becomes the package manifest. This removes the current requirement for Native Library Interop apps to repeat dependencies manually.

Graph export must use a separate, always-applied SDK init/plugin input rather than _AGPInitScriptPath, because that existing build-directory script is user-overridable.

MSBuild target flow

Resolution and reconciliation must occur at different points. Package AARs are discovered by _ResolveAars after ResolveReferences, while classic embedded resources are consumed later by _ResolveLibraryProjectImports.

NuGet restore / ResolvePackageAssets
  -> collect app and buildTransitive declarations
  -> collect recursive project-reference manifests
  -> build AndroidGradleProject modules and export their manifests
  -> normalize one request model
  -> run Gradle resolution when inputs changed
  -> emit selected direct AndroidLibrary items with JavaArtifact metadata
  -> _CategorizeAndroidLibraries
  -> ResolveReferences / _ResolveAars discovers package AAR providers
  -> inventory AndroidAarLibrary, AndroidJavaLibrary, and package/reference paths
  -> reconcile Gradle selections with local and legacy providers
  -> _ResolveLibraryProjectImports
  -> existing extraction, manifest/resource merge, duplicate checks, D8/R8

The request/resolve phase can run after _BuildAndroidGradleProjects and before _CategorizeAndroidLibraries. A separate reconciliation phase must run after _ResolveAars and before _ResolveLibraryProjectImports; NuGet package AARs are added directly to AndroidAarLibrary, not AndroidLibrary.

Explicit DependsOnTargets relationships should order the new targets relative to _MavenRestore, _VerifyJavaDependencies, _ResolveAars, and _ResolveLibraryProjectImports. Import order should not define behavior.

Resolver-emitted items must carry JavaArtifact so Java Dependency Verification recognizes them.

Application versus library builds

The full runtime graph should resolve only for an application head.

Library/binding projects should:

  • Continue using existing direct Maven/Gradle acquisition for binding inputs.
  • Invoke the synthetic resolver only when explicitly needing a compile/compile-only graph.
  • Publish requirements and boundAgainst data rather than embedding the runtime closure.
  • Mark resolver-produced inputs Bind=false, Pack=false, and exclude them from _CreateAarInputs.

This avoids one Gradle invocation per class library and prevents transitive runtime dependencies from leaking into every binding AAR/NuGet package.

Incrementality and performance

Inputs should include only the normalized data actually consumed:

  • project.assets.json
  • Imported package manifests
  • Recursive project-reference manifests
  • Exported AndroidGradleProject manifests
  • App declarations/overrides
  • Repository/mirror configuration
  • Lock and verification policy
  • Resolver plugin and pinned Gradle version

Real outputs should be used rather than a stamp:

  • obj/<configuration>/<tfm>/android-maven/graph.json
  • obj/<configuration>/<tfm>/android-maven/compile/
  • obj/<configuration>/<tfm>/android-maven/runtime/

JSON and scripts should be byte-stable: stable ordering, no timestamps, no avoidable absolute paths, and write-only-when-changed behavior.

The Gradle task should:

  • Use distinct compile/runtime artifact views.
  • Use deterministic Sync outputs.
  • Avoid applying AGP.
  • Avoid invocation when MSBuild inputs are unchanged.
  • Map runtime artifacts to AndroidAarLibrary/AndroidJavaLibrary.
  • Map compile-only artifacts to reference/binding classpaths without sending them to D8/R8.
  • Set AndroidSkipResourceProcessing=false on runtime AARs to match _ResolveAars.

Design-time builds must not initiate network access; they should reuse the last graph and let a normal build refresh it.

Gradle provisioning

The synthetic resolver should not depend on a system Gradle installation, a customer wrapper, or a wrapper restored from a package.

Use an SDK-pinned, checksum-verified Gradle distribution and local SDK plugin. Tie the resolver version to the workload rather than current Gradle.

Run it with an SDK-controlled Gradle user home, separate from ~/.gradle, so user init.d scripts cannot mutate resolution. Credentials should be bridged explicitly through approved environment/property inputs.

A dedicated resolver daemon under the isolated home could improve local builds, with a CI/no-daemon mode. The implementation spike should compare this with the Tooling API and pin/test the Gradle/JDK compatibility matrix.

AndroidGradleProject continues honoring the native project's own wrapper because it builds that project's code/plugins.

Locking, verification, offline use, and repositories

Expose an SDK-owned stable lock such as android.dependencies.lock.json, recording:

  • Requested and selected versions.
  • Repository identity/canonical source.
  • Artifact and metadata SHA-256.
  • Provider: Maven, package, project, or local artifact.
  • Contributing dependency paths.
  • Resolver schema/version.

Use Gradle's native dependency locking in strict mode as enforcement. The SDK JSON is the stable UX/provenance projection and can generate native Gradle lock/verification files under obj.

Policy:

  • Apps check in locks; libraries publish requirements/ranges.
  • Locked mode fails for missing, extra, changed, or checksum-mismatched components.
  • Provide explicit update tooling.
  • Require SHA-256 in locked mode.
  • Treat the same coordinate/version with different content as a supply-chain error.
  • --offline must fail clearly on a missing coordinate.
  • Support a shared read-only cache plus writable delta.
  • Support repository mirrors, including the existing dnceng dotnet-public-maven model.

For custom/private repositories:

  1. Packages may declare URL, stable ID, and mandatory group/module filters.
  2. Central and Google are pre-approved.
  3. A transitive custom repository is not contacted until the app approves it.
  4. Apps can replace URLs with enterprise mirrors.
  5. Credentials are referenced by identity and bridged from environment variables, an approved property-file path, or a provider.
  6. Secrets never enter generated JSON, configuration-cache fingerprints, binlogs, or normal diagnostics.
  7. HTTP requires explicit insecure opt-in.
  8. Content filters reduce dependency confusion and private-name leakage.

The synthetic resolver should not need remote Gradle plugins because the SDK plugin is local.

Mixed-mode compatibility

The application can contain:

  1. New coordinate-only packages.
  2. Coordinate-plus-payload packages.
  3. Modern packages with loose AAR/JAR and artifact= tags.
  4. Xamarin.Build.Download packages.
  5. Classic embedded-resource bindings.
  6. Unknown local AAR/JAR files.
  7. Patched, shaded, or source-built binaries without public Maven equivalents.

Identity precedence:

  1. Explicit manifest or JavaArtifact coordinates.
  2. Known package mapping data.
  3. Existing NuGet artifact= tags.
  4. In-archive Maven metadata for diagnostics only.
  5. Optional checksum lookup for diagnostics only.

Never alter the graph based only on inferred identity.

Reconciliation rules:

  • If a local provider declares the selected coordinate/version and expected checksum, use it and avoid the downloaded duplicate.
  • A known modified/patched provider is used only when explicitly declared with its checksum.
  • Selecting a different version than a binding's boundAgainst is an error unless declared compatible or explicitly overridden unsafely.
  • Unknown artifacts remain and flow to existing duplicate checks.
  • Two providers claiming the same coordinate/version with different hashes fail.

Reconciliation must enumerate the complete restored package graph from project.assets.json and package nuspecs. Looking only at direct PackageReference items misses transitive legacy packages.

Coordinate-aware reconciliation should run before _CheckDuplicateJavaLibraries; the current filename/content check remains as fallback.

dotnet/android-libraries migration

The repository currently generates about 701 NuGet packages from config.json:

  • Roughly 520 carry AAR/JAR payloads.
  • Roughly 180 proprietary-license families already use Xamarin.Build.Download at app build time.
  • AndroidX is the largest family.
  • Several packages include source-built shim JARs or modified AARs.

All generated packages already carry Maven coordinates in PackageTags, and target files already go to build/ and buildTransitive/ TFM folders.

Phase A: metadata only

Add Binderator support for:

  • android-maven-manifest.json
  • Conventionally imported buildTransitive data
  • provides hashes and modified flags
  • Repository identity
  • Direct/transitive requirements
  • Exclusions and extra dependencies
  • Multi-artifact manifests

No payload change.

Phase B: dual mode

Keep the payload and publish coordinates. New resolution can use the packaged file as a local provider; old SDKs remain unchanged.

Add global/per-artifact embedArtifacts configuration and explicit modified/published-artifact identity.

Phase C: low-risk coordinate-only pilot

Pilot a small Maven Central family with redistributable licensing, no shim, no AAR mutation, simple packaging, and good runtime tests. Do not begin with AndroidX or proprietary Google packages.

Phase D: replace Xamarin.Build.Download

The proprietary set is already downloaded at app build. Replace XBD declarations while preserving no-redistribution behavior, hash verification, Google mirror redirection, cache/offline diagnostics, and license notices.

Phase E: AndroidX

Before removing payloads, verify with regression tests that resolver-supplied AARs retain current behavior:

  • Consumer extraction already skips non-runtime AAR JARs such as lint.jar and api.jar.
  • Consumer extraction already handles proguard.txt; verify AGP 9 consumer-rule behavior.
  • AndroidX atomic/alignment metadata may select a different coherent family version.
  • Existing all-package tests document known collisions.

Permanent local-provider exceptions likely remain for source-built com.xamarin.* shims, patched/shaded binaries, frozen/relocated artifacts, and special vendor licensing/authentication cases. Exceptions should still publish provenance and requirements.

Diagnostics

Always write obj/<tfm>/android-maven/graph.json containing:

  • Selected components.
  • Every request/constraint and origin.
  • Selection reasons.
  • Repository and checksums.
  • Local-provider decisions.
  • Overrides, exclusions, substitutions, and capabilities.
  • Unresolved dependencies.

Provide:

  • dotnet build -t:AndroidMavenDependencyInsight -p:AndroidMavenInsight=group:artifact
  • A graph/list target for support bundles.
  • Stable XA diagnostics for conflicts, unknown provenance, unapproved repositories, lock drift, checksum mismatch, offline misses, and unsafe overrides.
  • Existing Microsoft NuGet package suggestions where useful.

Every Gradle declaration should include because("contributed by ...") so native dependencyInsight is useful.

Suggested modes:

  • Legacy: current behavior only.
  • Report: resolve/analyze but do not replace legacy payloads.
  • Resolve: Gradle graph owns known-coordinate selection.

Start with Legacy or Report; consider a future TFM-gated Resolve default only after package metadata and compatibility goals are met. Older TFMs keep legacy defaults.

Proposed workstreams

  1. Schema/API: item names, manifest/graph schemas, version and override semantics.
  2. Gradle resolver: local binary plugin, Android attributes, compile/runtime graphs, provisioning, isolated home, process model, cache/offline.
  3. MSBuild: collection, early resolution, late reconciliation, item mapping, incrementality, and preventing library closure leakage.
  4. Propagation: NuGet buildTransitive manifests, recursive project-reference outputs, file sidecars.
  5. Mixed mode: complete package inventory, coordinate/checksum matching, fallback diagnostics.
  6. Supply chain: native locking, stable lock UX, checksums, repository approval/filtering, credentials, mirrors.
  7. Diagnostics: graph JSON, dependency insight, stable XA messages, support bundles, rollout modes.
  8. Ecosystem pilot: Binderator packages, Native Library Interop, MAUI, then one low-risk Maven Central family.

Validation matrix

Resolver correctness:

  • Direct/transitive AAR/JAR closure.
  • Android attributes: Guava Android vs JRE, Kotlin Android vs JVM, KMP Android variants.
  • POM and Gradle Module Metadata.
  • Parent POMs/imported BOMs.
  • Rich versions/rejects/strict ranges/platforms.
  • Optional/provided/runtime scopes.
  • Classifiers/nonstandard names.
  • Relocations/substitutions/capabilities/exclusions.

Compatibility:

  • Legacy embedded package.
  • Modern loose payload.
  • PackageTags-only identity.
  • Xamarin.Build.Download.
  • Classic embedded resources.
  • Unknown local artifact.
  • Patched AAR/source-built shim.
  • Multi-artifact packages.
  • Package, project, and file references.

Build/security/runtime:

  • Central, Google, authenticated Basic/header repository, mirrors, and content filters.
  • HTTP denial, credential failures without disclosure, checksum mismatch.
  • Cold/warm/offline/locked builds.
  • No-op incremental build with no Gradle invocation.
  • Large solution with many class libraries but one app resolver invocation.
  • Design-time build with no network.
  • Windows/macOS/Linux; CoreCLR/NativeAOT; APK/AAB.
  • Device tests for class availability, AndroidX type movement, Kotlin alignment, manifests/resources/consumer rules, native ABIs, and R8/D8 failures.

No default-mode change should occur until no-op builds invoke neither Gradle nor the network.

Rollout gates

  1. Design approval: declarative-only boundary, API names, Gradle-highest semantics, boundAgainst policy, repository approval.
  2. Resolver spike: correct Android/JVM/KMP variant selection without applying AGP, stable graph, task integration, warm offline, no-op incrementality.
  3. Report-only preview: no payload suppression and identical existing builds.
  4. Metadata coverage: generated manifests and dual-mode packages compatible with old/new SDKs.
  5. Opt-in resolve pilot: Native Library Interop, MAUI, low-risk Maven Central family, authenticated fixture.
  6. Coordinate-only pilot: remove payload only from validated packages with rollback by package version/mode.
  7. Future TFM default: only after mixed-mode provenance, locks/offline/mirrors, performance, AndroidX behavior, and first-party package coverage are proven.

Open design decisions

  1. Extend AndroidMavenLibrary or add AndroidMavenDependency?
  2. What tested compatibility declaration is required before a coordinate-only package can float from boundAgainst?
  3. Ship Gradle in the workload or checksum-download it on first use?
  4. Final lock-file schema and whether native verification metadata is user-visible.
  5. Custom repository approval UX for noninteractive CI.
  6. Is an app-owned binary plugin sufficient, or is an app-owned script escape hatch also required?
  7. File-reference sidecar discovery/copy behavior.
  8. Should report mode initially be default or opt-in?
  9. Which capability/substitution rules belong in the SDK versus package manifests?
  10. Long-term relationship between Java Dependency Verification and full graph resolution.

Recommended first implementation slice

  1. AndroidMavenDependency with exact/require versions and Central/Google.
  2. SDK-owned resolver without applying AGP, but with Android variant attributes.
  3. One JSON request and graph output.
  4. Separate compile/runtime outputs mapped into existing item paths with Bind=false, Pack=false, JavaArtifact, and correct resource metadata.
  5. One recursive project-reference manifest and one buildTransitive package manifest.
  6. Report-only conflict diagnostics with origins.
  7. Preserve schema extension points for later lock/auth/custom-repository support.
  8. Mirror-backed fixtures and a no-op incremental test.

After that validates target ordering and packaging, add locking/auth and mixed-mode suppression before any package drops its payload.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementProposed change to current functionality.needs-triageIssues that need to be assigned.proposal

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions