Skip to content

Bindings via Gradle/Maven? #4528

@jonpryor

Description

@jonpryor

It would be "interesting"/cool if, instead of binding a .jar or .aar file, we could instead bind a gradle or maven "artifact name" and all dependencies within one binding project.

Is this at all practical or possible?


jpobst:

I've been giving this some thought recently, and maybe we should do less automatically for the user (since that's where the dragons live), and focus first on helping the user ensure they are creating correct bindings.

TL:DR

We will initially focus on tackling two pain points of binding from Maven:

  • Acquiring the .jar/.aar and the related .pom from Maven
  • Using the .pom to verify that required Java dependencies are being fulfilled

Let's take an example: Square's okhttp3 version 4.10.0 available in Maven.

Editor's note: we have an official binding for this library, but for the sake of an example.

Target Package

For the sake of simplicity, let's work from Maven directly instead of gradlew:

// Include is {group}:{id} because it has to be unique
// We can start with supporting Central and Google, maybe a URL if that's all we need for unauthenticated repositories
// Repository default is "Central", Bind and Pack default is "True", shown for completeness

<MavenAndroidLibrary Include="com.squareup.okhttp3:okhttp" Version="4.10.0" Repository="Central" Bind="True" Pack="True" />

With this information, we can use MavenNet to download:

We can also allow this to come from somewhere on the file system if the user provides a .jar/.aar and a .pom:

// Syntax is debatable
<LocalAndroidLibrary Include="com.squareup.okhttp3:okhttp" Version="4.10.0" Package="okhttp-4.10.0.jar" Pom="okhttp-4.10.0.pom" />

Because we are retrieving a .pom, we can start to do some dependency work for the user with a custom MSBuild task, and if they compile at this point they will receive the following build errors:

Error XA0000: Dependency 'com.squareup.okio:okio-jvm' version '3.0.0' not satisfied.
Error XA0000: Dependency 'org.jetbrains.kotlin:kotlin-stdlib' version '1.6.20' not satisfied.

Dependencies

In our example above, we saw that we can prevent the user from missing dependencies by requiring a .pom file. Dependencies can be fulfilled in 4 ways:

  • NuGet <PackageReference>
  • Included in this binding via <MavenAndroidLibrary> or <LocalAndroidLibrary>
  • Another bindings project <ProjectReference>
  • Explicitly ignored

NuGet <PackageReference>

Automatically finding matching NuGet packages is out of scope, but no matter how they get here, we can validate that they meet the required dependency. This is the preferred mechanism because it uses a single canonical package and won't cause multiple versions of Java artifacts to conflict in our application. Additionally, NuGet packages have already taken care of transitive dependencies, so the user doesn't need to worry about them.

We definitely have a package for Kotlin Stdlib >= 1.6.20, so let's use that:

<PackageReference Include="Xamarin.Kotlin.StdLib" Version="1.7.10" />

Ideally, we'll use package metadata or a metadata file inside the package to tell us the Java version that this package binds (1.7.10). This will appease our tooling and when the user rebuilds, the Kotlin dependency error will disappear.

We can also allow the user to manually specify what Java dependency a NuGet package fulfills if needed:

// Q: Will VS tooling preserve our metadata if it updates the package?
<PackageReference Include="Xamarin.Kotlin.StdLib" Version="1.7.10" JavaArtifact="org.jetbrains.kotlin:kotlin-stdlib" JavaVersion="1.7.10" />

Included in this binding via <MavenAndroidLibrary> or <LocalAndroidLibrary>

If there isn't a NuGet package for our Java dependency, we can use the same mechanism to add an additional artifact to this project:

// Bind = false if we don't need a C# binding for this one
<MavenAndroidLibrary Include="com.squareup.okio:okio-jvm" Version="3.2.0" Repository="Central" Bind="False" Pack="True" />

Unlike NuGet packages, this package's dependencies are not covered, so adding this package adds new dependency errors:

Error XA0000: Dependency 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' version '1.6.20' not satisfied.
Error XA0000: Dependency 'org.jetbrains.kotlin:kotlin-stdlib-common' version '1.6.20' not satisfied.

These will now need to be resolved too.

Another bindings project <ProjectReference>

The user could also have a <ProjectReference> to another Android Bindings library that fulfills a dependency.

<ProjectReference Include="../okio/okio.csproj" JavaArtifact="com.squareup.okio:okio-jvm" JavaVersion="3.2.0" />

TODO: Would the user need to specify JavaArtifact and JavaVersion or could we fish it out of the project somehow?

In this scenario, transitive dependencies would not be checked, the referenced project is expected to take care of itself.

Explicitly ignored

Sometimes the user knows better than us (or thinks they do), and we should allow them to explicitly ignore a dependency.

<MavenIgnoredDependency Include="com.squareup.okio:okio-jvm" Version="3.2.0" />

Benefits

Updateable

Unlike a "build it once" system, when a new version of the package is released the user simply needs to change the version number:

<MavenAndroidLibrary Include="com.squareup.okhttp3:okhttp" Version="5.0.0" />

If the new version needs newer dependencies, the user will be required to update them rather than be allowed to build a package with bad dependencies.

Error XA0000: Dependency 'com.squareup.okio:okio-jvm' version '4.0.0' not satisfied.

Extensible

This system currently avoids the dragons of "find dependencies for the user", but that could be added later when we think we've solved it. The dependency verification outlined here would still be essential in that scenario as well.

Simpler?

Does not involve gradlew or Android Studio.

Limitations

  • Authenticated Maven feeds are out of scope and will not be usable.

Metadata

Metadata

Assignees

Labels

Area: BindingsIssues in Java Library Binding projects.enhancementProposed change to current functionality.

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions