dotnet / android

.NET for Android provides open-source bindings of the Android SDK for use with .NET managed languages such as C#
MIT License
1.94k stars 533 forks source link

_CompileJava error: package does not exist #8774

Closed tuyen-vuduc closed 9 months ago

tuyen-vuduc commented 9 months ago

Android application type

.NET Android (net7.0-android, net8.0-android, etc.)

Affected platform version

macos-12, .NET 8.0.200, .NET 7.0.406

Description

I tried to set up CI/CD to build and archive my .NET Android application.

It works well on my local computer, both MacOS and Windows (latest).

However, both AppCenter and GitHub Actions faced the same problem.

2024-02-29T05:14:04.7519950Z          /Users/runner/hostedtoolcache/Java_Microsoft_jdk/11.0.19/x64/Contents/Home/bin/javac -J-Dfile.encoding=UTF8 "@/var/folders/h1/8hndypj13nsbj5pn4xsnv1tm0000gn/T/tmpJpYFz1.tmp" -target 1.8 -source 1.8 
2024-02-29T05:14:05.7535370Z          obj/release/net7.0-android/android/src/crc640c59d0cc10ab0a40/MainActivity.java:8: error: package com.braintreepayments.api does not exist
2024-02-29T05:14:05.7607060Z                com.braintreepayments.api.DropInListener

The source code to check out can be forked/cloned from here.

Steps to Reproduce

  1. Fork the repository
  2. Build the repository with GitHub Actions or AppCenter

Did you find any workaround?

No.

Relevant log output

2024-02-29T05:14:04.7517850Z        _CompileJava:
2024-02-29T05:14:04.7519950Z          /Users/runner/hostedtoolcache/Java_Microsoft_jdk/11.0.19/x64/Contents/Home/bin/javac -J-Dfile.encoding=UTF8 "@/var/folders/h1/8hndypj13nsbj5pn4xsnv1tm0000gn/T/tmpJpYFz1.tmp" -target 1.8 -source 1.8 
2024-02-29T05:14:05.7535370Z          obj/release/net7.0-android/android/src/crc640c59d0cc10ab0a40/MainActivity.java:8: error: package com.braintreepayments.api does not exist
2024-02-29T05:14:05.7607060Z                com.braintreepayments.api.DropInListener
2024-02-29T05:14:05.7609300Z                                         ^
2024-02-29T05:14:05.7612650Z          obj/release/net7.0-android/android/src/crc640c59d0cc10ab0a40/MainActivity.java:56: error: package com.braintreepayments.api does not exist
2024-02-29T05:14:05.7616590Z            public void onDropInSuccess (com.braintreepayments.api.DropInResult p0)
2024-02-29T05:14:05.7619580Z                                                                  ^
2024-02-29T05:14:05.7622940Z          obj/release/net7.0-android/android/src/crc640c59d0cc10ab0a40/MainActivity.java:61: error: package com.braintreepayments.api does not exist
2024-02-29T05:14:05.7626440Z            private native void n_onDropInSuccess (com.braintreepayments.api.DropInResult p0);
2024-02-29T05:14:05.7628950Z                                                                            ^
2024-02-29T05:14:05.8283360Z          obj/release/net7.0-android/android/src/mono/com/braintreepayments/cardform/OnCardFormSubmitListenerImplementor.java:8: error: package com.braintreepayments.cardform does not exist
2024-02-29T05:14:05.8289340Z                com.braintreepayments.cardform.OnCardFormSubmitListener
2024-02-29T05:14:05.8291770Z                                              ^
2024-02-29T05:14:05.8297170Z          obj/release/net7.0-android/android/src/mono/com/braintreepayments/cardform/OnCardFormValidListenerImplementor.java:8: error: package com.braintreepayments.cardform does not exist
2024-02-29T05:14:05.8301390Z                com.braintreepayments.cardform.OnCardFormValidListener
2024-02-29T05:14:05.8304930Z                                              ^
2024-02-29T05:14:05.8308900Z          obj/release/net7.0-android/android/src/mono/com/braintreepayments/cardform/OnCardFormFieldFocusedListenerImplementor.java:8: error: package com.braintreepayments.cardform does not exist
2024-02-29T05:14:05.8316300Z                com.braintreepayments.cardform.OnCardFormFieldFocusedListener
2024-02-29T05:14:05.8318830Z                                              ^
2024-02-29T05:14:05.8323010Z          obj/release/net7.0-android/android/src/mono/com/braintreepayments/api/VenmoListenerImplementor.java:8: error: package com.braintreepayments.api does not exist
2024-02-29T05:14:05.8328530Z                com.braintreepayments.api.VenmoListener
tuyen-vuduc commented 9 months ago

Build log

dotnet-braintree-quickstart.dotnet8.github.actions.txt

dellis1972 commented 9 months ago

I'm not sure but this might be a problem https://github.com/tuyen-vuduc/dotnet-binding-utils/commit/a4e426faafaf5a76b99566a35f23e289685d85ad.

under .net 6+ library projects now produce an .aar file along side the .dll. They are no longer embedded into the dll. This .aar file contains the classes.jar file and other items which are needed to compile the java side of the binding. So its best to ship this .aar with the nuget when targetting net6.0-android and net7.0-android etc. The old MonoAndroidxx targets will still have the files embedded.

See https://github.com/xamarin/xamarin-android/blob/main/Documentation/guides/OneDotNetEmbeddedResources.md for details

tuyen-vuduc commented 9 months ago

@dellis1972 The Android library isn't embedded in the DLL. In my created bindings, I leverage Gradle to download them the Android/Java way. It works well in a development machine, but not in a build server on GitHub/AppCenter.

If you look at my attached log, I already list out all .gradle's cached files, but not sure why they cannot be recognized.

Anyhow a permission prevents the access???

dellis1972 commented 9 months ago

If it is a permission issue on CI (which I have no idea) I probably would not use this code https://github.com/tuyen-vuduc/dotnet-braintree-quickstart/blob/main/DotNetAndroid.BraintreeDropInQs/DotNetAndroid.BraintreeDropInQs.csproj#L40.

You will probably be better off passing in the GradleFolderPath from your CI script and use a local directory rather than the users home folder.

if you change the code to be

<GradleFolderPath Condition=" '$(GradleFolderPath)' == ''">$(UserHome)/.gradle</GradleFolderPath>

you can then use the dotnet build -c $Configuration -v $Verbosity -p:JavaSdkDirectory="$JAVA_HOME" -p:GradleFolderPath=./gradle in your workflow to override the default path (which will still work on your machine).

tuyen-vuduc commented 9 months ago

No, that's one is just for listing down to see if the expected folders and files exist. It's not for the binding libraries.

And it actually refers to how I leverages Gradle.

Gradle will download and cache files in ~/.gradle/caches/modules-2/files-2.1/. The binding libraries have TARGETS files to point the native libraries to that path already.

Here is an example

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <_ComBraintreepaymentsApiDropIn>true</_ComBraintreepaymentsApiDropIn>
  </PropertyGroup>  
  <ItemGroup>
    <AndroidLibrary 
      Condition=" '$(OS)' == 'Unix' and Exists('$(Home)/.gradle/caches/modules-2/files-2.1/com.braintreepayments.api/drop-in/6.14.0/ad4d7e66366e11a1caf27283519952f1de2e2bf4/drop-in-6.14.0.aar') " 
      Include="$(Home)/.gradle/caches/modules-2/files-2.1/com.braintreepayments.api/drop-in/6.14.0/ad4d7e66366e11a1caf27283519952f1de2e2bf4/drop-in-6.14.0.aar" 
      Bind="false" Pack="false" Visible="false"/>
    <AndroidLibrary       
      Condition=" '$(OS)' != 'Unix' and Exists('$(UserProfile)/.gradle/caches/modules-2/files-2.1/com.braintreepayments.api/drop-in/6.14.0/ad4d7e66366e11a1caf27283519952f1de2e2bf4/drop-in-6.14.0.aar') " 
      Include="$(UserProfile)/.gradle/caches/modules-2/files-2.1/com.braintreepayments.api/drop-in/6.14.0/ad4d7e66366e11a1caf27283519952f1de2e2bf4/drop-in-6.14.0.aar" 
      Bind="false" Pack="false" Visible="false" />
    <GradleImplementation 
      Condition=" $(_ComBraintreepaymentsApiDropIn) == 'true' " 
      Include="com.braintreepayments.api:drop-in:6.14.0" />
  </ItemGroup>
</Project>

Have you checked out at your machine and you find it work well?

tuyen-vuduc commented 9 months ago

By checking the out put of ls command, I can see there is AAR file

image

dellis1972 commented 9 months ago

This doesn't seem like a bug in .net Android? All the Gradle stuff seems to be from https://github.com/tuyen-vuduc/dotnet-binding-utils/.

tuyen-vuduc commented 9 months ago

@dellis1972 Can you kindly look at it and check what the problem could be by actually checking the sample source code.

Gradle, its job, is simply download the native libraries to the machine. It's much more efficient that using Xamarin.Build.Download. The only different is the path to the native library.

tuyen-vuduc commented 9 months ago

Additional info: Github Actions for dotnet-binding-utils works without any issues with referencing to .gradle folder's contents.

Can it be any problem with the linker?

dellis1972 commented 9 months ago

So my guess is this code

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <_ComBraintreepaymentsApiDropIn>true</_ComBraintreepaymentsApiDropIn>
  </PropertyGroup>  
  <ItemGroup>
    <AndroidLibrary 
      Condition=" '$(OS)' == 'Unix' and Exists('$(Home)/.gradle/caches/modules-2/files-2.1/com.braintreepayments.api/drop-in/6.14.0/ad4d7e66366e11a1caf27283519952f1de2e2bf4/drop-in-6.14.0.aar') " 
      Include="$(Home)/.gradle/caches/modules-2/files-2.1/com.braintreepayments.api/drop-in/6.14.0/ad4d7e66366e11a1caf27283519952f1de2e2bf4/drop-in-6.14.0.aar" 
      Bind="false" Pack="false" Visible="false"/>
    <AndroidLibrary       
      Condition=" '$(OS)' != 'Unix' and Exists('$(UserProfile)/.gradle/caches/modules-2/files-2.1/com.braintreepayments.api/drop-in/6.14.0/ad4d7e66366e11a1caf27283519952f1de2e2bf4/drop-in-6.14.0.aar') " 
      Include="$(UserProfile)/.gradle/caches/modules-2/files-2.1/com.braintreepayments.api/drop-in/6.14.0/ad4d7e66366e11a1caf27283519952f1de2e2bf4/drop-in-6.14.0.aar" 
      Bind="false" Pack="false" Visible="false" />
    <GradleImplementation 
      Condition=" $(_ComBraintreepaymentsApiDropIn) == 'true' " 
      Include="com.braintreepayments.api:drop-in:6.14.0" />
  </ItemGroup>
</Project>

is being evaluated before the Gradle stuff has been downloaded. As a result your condition will not include a file which does not exist yet. ItemGroups and PropertyGroups outside of a Target like this are evaluated before any targets are run. Also the fact that you get a clean machine on CI each time will means the "cache" is not there. That's probably why it works locally, because you have the cache in place.

You should put the AndroidLibrary ItemGroups in a Target which runs after the _GradleSyncTarget otherwise the files will just not be there. ItemGroups within Targets are evaluated when the Target is run, not when the project is loaded.

<Target Name="_IncludeStuff" AfterTargets="_GradleSyncTarget">
    <ItemGroup>
    <AndroidLibrary 
      Condition=" '$(OS)' == 'Unix' and Exists('$(Home)/.gradle/caches/modules-2/files-2.1/com.braintreepayments.api/drop-in/6.14.0/ad4d7e66366e11a1caf27283519952f1de2e2bf4/drop-in-6.14.0.aar') " 
      Include="$(Home)/.gradle/caches/modules-2/files-2.1/com.braintreepayments.api/drop-in/6.14.0/ad4d7e66366e11a1caf27283519952f1de2e2bf4/drop-in-6.14.0.aar" 
      Bind="false" Pack="false" Visible="false"/>
    <AndroidLibrary       
      Condition=" '$(OS)' != 'Unix' and Exists('$(UserProfile)/.gradle/caches/modules-2/files-2.1/com.braintreepayments.api/drop-in/6.14.0/ad4d7e66366e11a1caf27283519952f1de2e2bf4/drop-in-6.14.0.aar') " 
      Include="$(UserProfile)/.gradle/caches/modules-2/files-2.1/com.braintreepayments.api/drop-in/6.14.0/ad4d7e66366e11a1caf27283519952f1de2e2bf4/drop-in-6.14.0.aar" 
      Bind="false" Pack="false" Visible="false" />
   </ItemGroup>
</Target>

You need to make sure each "nuget" has its own unique Target though, since they will overwrite each other, so if you have multiple "_IncludeStuff" Targets across various nuget packages, only the last one imported will run.

tuyen-vuduc commented 9 months ago

@dellis1972 Thís info is very helpful. Let me check it out then let you know.

tuyen-vuduc commented 9 months ago

@dellis1972 Putting a target inside a TARGETS doesn't work on my check. Have you ever tried before?

tuyen-vuduc commented 9 months ago

@dellis1972 The solution is to remove the condition of checking if the file exists. That file must exist; otherwise, error by the compiler.

<ItemGroup>
      <AndroidLibrary 
        Condition=" '$(OS)' == 'Unix' "
        Pack="False"
        Bind="False"
        Visible="False"
        Include="$(Home)/.gradle/caches/modules-2/files-2.1/com.braintreepayments.api/card/4.40.1/a26e87a3c923c8f9ce6e3444ca7e3cf34da6c53f/card-4.40.1.aar" >
        <AndroidXSkipAndroidXMigration>true</AndroidXSkipAndroidXMigration>
      </AndroidLibrary>
      <AndroidLibrary       
        Condition=" '$(OS)' != 'Unix' "
        Include="$(UserProfile)/.gradle/caches/modules-2/files-2.1/com.braintreepayments.api/card/4.40.1/a26e87a3c923c8f9ce6e3444ca7e3cf34da6c53f/card-4.40.1.aar" 
        Pack="False"
        Bind="False"
        Visible="False">
        <AndroidXSkipAndroidXMigration>true</AndroidXSkipAndroidXMigration>
      </AndroidLibrary>
    </ItemGroup>

Thanks a lot for your info and the support. It provides me more insights.