dotnet / dotnet/android

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

Open
#12,495 3 comments 0 reactions 0 assignees View on GitHub
enhancement needs-triage proposal
Dominant language
C#
Stars
2.1k
Forks
579
Avg merge
1d 19h
Merged PRs (30d)
252

Description

## 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:

- #4528: original Gradle/Maven binding discussion.
- #9013: multi-artifact Java dependency metadata.
- #9974 and #10481: missing JARs leading to `NoClassDefFoundError` after package consumption.
- #11874: future slim-binding ergonomics.

### 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`

```xml

```

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

```xml

```

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:

```json
{
"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//.props`, or import the data fragment from an existing `.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`.

```text
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///android-maven/graph.json`
- `obj///android-maven/compile/`
- `obj///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//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

- [`AndroidMavenLibrary`](https://learn.microsoft.com/dotnet/android/binding-libs/advanced-concepts/android-maven-library)
- [Java dependency verification](https://learn.microsoft.com/dotnet/android/binding-libs/advanced-concepts/java-dependency-verification)
- [Resolving Java dependencies](https://learn.microsoft.com/dotnet/android/binding-libs/advanced-concepts/resolving-java-dependencies)
- [Android build items](https://learn.microsoft.com/dotnet/android/building-apps/build-items)
- [Distributing bindings](https://learn.microsoft.com/dotnet/android/binding-libs/advanced-concepts/distributing)
- [CommunityToolkit Native Library Interop](https://github.com/CommunityToolkit/Maui.NativeLibraryInterop)
- [`dotnet/android-libraries`](https://github.com/dotnet/android-libraries)
- [Gradle rich versions](https://docs.gradle.org/current/userguide/dependency_versions.html)
- [Gradle constraints](https://docs.gradle.org/current/userguide/dependency_constraints.html)
- [Gradle platforms/BOMs](https://docs.gradle.org/current/userguide/platforms.html)
- [Gradle graph resolution](https://docs.gradle.org/current/userguide/graph_resolution.html)
- [Gradle capabilities](https://docs.gradle.org/current/userguide/component_capabilities.html)
- [Gradle repository centralization/filtering](https://docs.gradle.org/current/userguide/centralizing_repositories.html)
- [Gradle locking](https://docs.gradle.org/current/userguide/dependency_locking.html)
- [Gradle dependency verification](https://docs.gradle.org/current/userguide/dependency_verification.html)
- [Gradle configuration cache](https://docs.gradle.org/current/userguide/configuration_cache.html)
- [Gradle security guidance](https://docs.gradle.org/current/userguide/best_practices_security.html)
- [NuGet build/buildTransitive assets](https://learn.microsoft.com/nuget/concepts/msbuild-props-and-targets)
- [NuGet dependency resolution](https://learn.microsoft.com/nuget/concepts/dependency-resolution)
- [NuGet central package management](https://learn.microsoft.com/nuget/consume-packages/central-package-management)

Contributor guide

No contributing guide indexed for this repository

Research direction

No implementation files or tests are named. Start by reading the existing AndroidMavenLibrary, AndroidGradleProject, and Java.Interop.Tools.Maven entry points, then trace how dependency metadata reaches an app. Done means a reviewed design and staged implementation plan for a Gradle-backed final application graph with manifests, locking, offline behavior, and legacy compatibility.

Written by the indexing model from the issue text.

Assessment

Tech stack
android, csharp
Domain
build-system, mobile-dev, tooling
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.