Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bindings via Gradle/Maven? #4528

Closed
jonpryor opened this issue Feb 5, 2020 · 10 comments · Fixed by #8649
Closed

Bindings via Gradle/Maven? #4528

jonpryor opened this issue Feb 5, 2020 · 10 comments · Fixed by #8649
Assignees
Labels
Area: Bindings Issues in Java Library Binding projects. enhancement Proposed change to current functionality.
Milestone

Comments

@jonpryor
Copy link
Member

jonpryor commented Feb 5, 2020

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.
@jpobst jpobst transferred this issue from dotnet/java-interop Apr 6, 2020
@jpobst jpobst added Area: Bindings Issues in Java Library Binding projects. enhancement Proposed change to current functionality. labels Apr 6, 2020
@jpobst jpobst added this to the Under Consideration milestone Apr 6, 2020
@JonDouglas
Copy link
Contributor

@jonpryor That's something @EgorBo did awhile back:

https://github.com/EgorBo/Xamarin.GradleBindings

https://marketplace.visualstudio.com/items?itemName=EgorBogatov.XamarinGradleBindings

It's definitely something we hear from users often.

In other words, a tool that could generate a binding project based on a maven package & it's dependencies.

How feasible is it to create a binding project that gets a user say 90% the way there with their Metadata.xml to just consume in a project?

@bramborman
Copy link

Was just going to open an issue about the same feature and found this.. I really miss the VS extension as it doesn't support VS 2019 but having the functionality built-in would be much better.

The think I missed in the extension however was that it was just for generating a new binding project. There was no way (or I didn't find it) to update the .jar or .aar after the binding project was created. So if I was to maintain a binding library, when an update to the underlying native lib would come, I'd have to recreate the project or find another way to get the updated versions of these files after the project was created (which I found kinda hard compared to for example NuGet)...

So the imo best approach for this would be to make it work similarly to how NuGets are consumed by adding an MSBuild item like <GradleReference Include="GradleLibName" Version="1.2.3" />. Of course a graphical package manager similar to the NuGet one would be great, but at least such support to easily reference (and update by changing the version attribute) would be a huge step forward 😊.

@jaceDeng
Copy link

jaceDeng commented Apr 6, 2021

GradleReference is a good idea. Before I bind such a library, I had to download all gradle dependent AAR or jar files. If we do this through msbuild, I think it will greatly reduce our work of binding Android library.

@jpobst jpobst modified the milestones: Under Consideration, .NET 8 Sep 13, 2022
@jpobst jpobst self-assigned this Sep 13, 2022
@MagicAndre1981
Copy link

here is also a similar thing:

IKVM.Maven.Sdk - IKVM support for Maven dependencies
https://github.com/ikvm-revived/ikvm-maven#ikvmmavensdk---ikvm-support-for-maven-dependencies

IKVM.Maven.Sdk is a set of MSBuild extensions for referencing Maven artifacts within .NET SDK projects.

@jpobst
Copy link
Contributor

jpobst commented Sep 13, 2022

Interesting IKVM link. This is what I'm currently considering: #5352 (comment).

Moved above ^^

@jonpryor jonpryor modified the milestones: .NET 8, .NET 9 Planning Mar 27, 2023
@tuyen-vuduc
Copy link

This is a good idea. However, I think if .NET for Android can run as a part of the gradle process, it'll be much better. The same way that Flutter/RN do its job. We don't have to bind everything if we don't need to.

I am in process of building my library to support resolve native libraries using Gradle, so we don't have to embed them within the nuget packages.

ETA: July 2023

@tuyen-vuduc
Copy link

Forks, I finally made my approach public for everyone to use and enhance.

Please check it out here.

P/S: Sorry for no documentation yet, I am going to publish a how-to post in next few days.

@jpobst
Copy link
Contributor

jpobst commented Jul 25, 2023

I don't guess I ever posted a link on this issue. Here is the prototype of the approach mentioned in the issue description:
https://github.com/jpobst/Prototype.Android.MavenBindings

@tuyen-vuduc
Copy link

tuyen-vuduc commented Jul 25, 2023

@jpobst I checked your link previously, but it doesn't leverage Gradle yet. By downloading manually ourself, we recreate the wheel. Your prototype will far from complete if you also want to have maven repository authentication as well.

In my findings, many dependencies aren't even listed in POM, but still understood by gradle when building using Android Studio :D . Xamarin.Android/.NET Android has to adopt.

@jpobst jpobst changed the title Bindings via Gradle/Maven? Bindings via Gradle/Maven? (Part 1) Oct 19, 2023
@jpobst jpobst changed the title Bindings via Gradle/Maven? (Part 1) Bindings via Gradle/Maven? Oct 19, 2023
jonpryor pushed a commit that referenced this issue Nov 16, 2023
…#8420)

Context: #4528

Add support for the `@(AndroidMavenLibrary)` item group, which will
download the requested Java artifact version from a Maven repository
and add it as an `@(AndroidLibrary)` for binding.

For example, to download the Maven package
[pkg:maven/com.google.auto.value/[email protected]][0]
from Maven Central and create an Android binding for it:

	<ItemGroup>
	  <AndroidMavenLibrary Include="com.google.auto.value:auto-value-annotations" Version="1.10.4" />
	</ItemGroup>

~~ Specification ~~

The `//AndroidMavenLibrary/@Include` format is `{GroupId}:{ArtifactId}`.

Any additional attributes such as `%(Bind)` or `%(Pack)` will be copied
to the created `@(AndroidLibrary)` item.

`%(Bind)` and `%(Pack)` default to `true` and control the binding
process as specified for [`@(AndroidLibrary)`][1].

`%(Repository)` controls which Maven Repository to download artifacts
from.  Supported values include:

  * `Central` for [Maven Central][2].  This is the default value if
    `%(Repository)` is not specified.

  * `Google` for [Google's Maven][3].

  * A URL, for downloading from a custom Maven instance.
    *Note*: Maven authentication is *not* supported.

~~Examples:~~

	<ItemGroup>
	  <AndroidMavenLibrary 
	      Include="androidx.core:core" 
	      Version="1.9.0" 
	      Repository="Google"
	      Bind="false"
	  />
	  <AndroidMavenLibrary 
	      Include="com.github.chrisbanes:PhotoView" 
	      Version="2.3.0" 
	      Repository="https://repository.mulesoft.org/nexus/content/repositories/public"
	      Pack="false"
	  />
	</ItemGroup>

There are 2 parts to #4528:

 1. Downloading artifacts from Maven
 2. Ensuring all dependencies specified in the POM file are met

Only (1) is currently addressed. (2) will be addressed in the future.

[0]: https://repo1.maven.org/maven2/com/google/auto/value/auto-value-annotations/1.10.4/
[1]: https://github.com/xamarin/xamarin-android/blob/main/Documentation/guides/OneDotNetEmbeddedResources.md#msbuild-item-groups
[2]: https://repo1.maven.org/maven2/
[3]: https://maven.google.com/web/index.html
@tuyen-vuduc
Copy link

I found that we have a great progress to have a new CSPROJ item to include a native lib from a Maven repo directly. However,

  • we have to use .NET9 to enable that feature
  • we cannot use a Maven repository protected with credentials

I managed to create my own NuGet package, Dependency.Gradle which we can use Gradle to download the dependencies for us and already use that one for all my recent binding libraries which can be found the source in the repo Dotnet Binding Utils.

I believe by using Gradle, it's much better and more aligned with the Android native.

jonpryor pushed a commit that referenced this issue Mar 22, 2024
Fixes: #4528

Context: 3659766
Context: dotnet/java-interop@1c9c8c9

Commit 3659766 mentioned:

> There are 2 parts to #4528:
> 
>  1. Downloading artifacts from Maven
>  2. Ensuring all dependencies specified in the POM file are met
> 
> Only (1) is currently addressed. (2) will be addressed in the future.

Implement support for (2): use `Java.Interop.Tools.Maven.dll` from
dotnet/java-interop@1c9c8c9c to download and parse
[Maven POM files][0] and ensure that all required Java dependencies
are fulfilled.

POM files are downloaded into `$(MavenCacheDirectory)` (3659766).

Java dependency verification is a critical step that users often miss
or make mistakes, resulting in non-functional bindings.

Consider a classlib project with the following snippet:

	<ItemGroup>
	  <AndroidMavenLibrary Include="com.squareup.okhttp3:okhttp" Version="4.9.3" />
	</ItemGroup>

With the new POM verification features, the above snippet will
produce the following errors:

	error XA4242: Java dependency 'com.squareup.okio:okio:2.8.0' is not satisfied. Microsoft maintains the NuGet package 'Square.OkIO' that could fulfill this dependency.
	error XA4242: Java dependency 'org.jetbrains.kotlin:kotlin-stdlib:1.4.10' is not satisfied. Microsoft maintains the NuGet package 'Xamarin.Kotlin.StdLib' that could fulfill this dependency.

This is accomplished by automatically downloading the POM file (and
any needed parent/imported POM files) for the specified artifact and
using them to determine all required dependencies.

There are many documented ways of fixing these errors, the best way
is using the latest versions of the suggested NuGet packages:

	<ItemGroup>
	  <PackageReference Include="Square.OkIO" Version="3.6.0.1" />
	  <PackageReference Include="Xamarin.Kotlin.StdLib" Version="1.9.22" />
	</ItemGroup>

Note this commit replaces our previous usage of `MavenNet` with
`Java.Interop.Tools.Maven`.  Thus it removes the `MavenNet` TPN.

Some future concerns:

  - Can we automatically determine which dependencies are met by a
    `@(ProjectReference)`?  Today, the user must manually add the
    metadata `%(ProjectReference.JavaArtifact)` and
    `%(ProjectReference.JavaVersion)` to specify this.

  - We use the link https://aka.ms/ms-nuget-packages to download
    [`microsoft-packages.json`][1], which is used to provide NuGet
    suggestions.  We need to figure out a permanent home for this
    file and a process for generating it.
    
    Luckily we can ship a preview for now and change the `aka.ms`
    link to point to the permanent home later.

[0]: https://maven.apache.org/pom.html
[1]: https://raw.githubusercontent.com/jpobst/Prototype.Android.MavenBindings/main/microsoft-packages.json
@github-actions github-actions bot locked and limited conversation to collaborators Apr 22, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Area: Bindings Issues in Java Library Binding projects. enhancement Proposed change to current functionality.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants