Skip to content

Commit b23d06e

Browse files
committed
[Hello-NativeAOTFromAndroid] Add NativeAOT+Android sample
Context: 2197579 Commit 2197579 added `samples/Hello-NativeAOTFromJNI`, which demonstrated the use of NativeAOT to create a native library which could be loaded and used by a Java application. What else supports loading native libraries for use by a "Java" environment? Android! Take the core infrastructure from `Hello-NativeAOTFromJNI`, have the binary output target `linux-bionic-arm64` -- the `$(RuntimeIdentifier)` for .NET using Android's "bionic" libc while *not* using .NET for Android -- and then use `gradlew` to package that native library into an Android application. Update `jcw-gen` to appropriately support `[JniTypeSignature(GenerateJavaPeer=false)]` when determining the constructors that a Java Callable Wrapper should contain, and to support method overridability. Update `generator` to support using `Java.Base.dll` as a referenced assembly for binding purposes, in particular by supporting the use of `[JniTypeSignatureAttribute]` on already bound types. TODO: cleanup the included Android sample. Do we *really* need all those resources?
1 parent 6cfa78c commit b23d06e

File tree

49 files changed

+1500
-6
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1500
-6
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using Microsoft.Build.Framework;
2+
using Microsoft.Build.Utilities;
3+
using System;
4+
using System.Linq;
5+
using System.IO;
6+
using System.Collections.Generic;
7+
8+
9+
namespace Java.Interop.BootstrapTasks
10+
{
11+
public class ParseAndroidResources : Task
12+
{
13+
public ITaskItem AndroidResourceFile { get; set; }
14+
public ITaskItem OutputFile { get; set; }
15+
public string DeclaringNamespaceName { get; set; }
16+
public string DeclaringClassName { get; set; }
17+
18+
public override bool Execute ()
19+
{
20+
using (var o = File.CreateText (OutputFile.ItemSpec)) {
21+
o.WriteLine ($"namespace {DeclaringNamespaceName};");
22+
o.WriteLine ();
23+
o.WriteLine ($"partial class {DeclaringClassName} {{");
24+
var resources = ParseAndroidResourceFile (AndroidResourceFile.ItemSpec);
25+
foreach (var declType in resources.Keys.OrderBy (x => x)) {
26+
o.WriteLine ($"\tpublic static class @{declType} {{");
27+
var decls = resources [declType];
28+
foreach (var decl in decls.Keys.OrderBy (x => x)) {
29+
o.WriteLine ($"\t\tpublic const int {decl} = {decls [decl]};");
30+
}
31+
o.WriteLine ("\t}");
32+
}
33+
o.WriteLine ("}");
34+
o.WriteLine ();
35+
}
36+
37+
return !Log.HasLoggedErrors;
38+
}
39+
40+
Dictionary<string, Dictionary<string, string>> ParseAndroidResourceFile (string file)
41+
{
42+
var resources = new Dictionary<string, Dictionary<string, string>> ();
43+
using (var reader = File.OpenText (file)) {
44+
string line;
45+
while ((line = reader.ReadLine ()) != null) {
46+
if (line.StartsWith ("#"))
47+
continue;
48+
var items = line.Split (' ');
49+
if (items.Length != 4)
50+
continue;
51+
var decl = items [1];
52+
var name = items [2];
53+
var value = items [3];
54+
if (!resources.TryGetValue (decl, out var declResources))
55+
resources.Add (decl, declResources = new Dictionary<string, string>());
56+
declResources.Add (name, value);
57+
}
58+
}
59+
60+
return resources;
61+
}
62+
}
63+
}

build-tools/scripts/Prepare.targets

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,12 @@
1515
<PropertyGroup>
1616
<_MaxJdk>$(MaxJdkVersion)</_MaxJdk>
1717
<_MaxJdk Condition=" '$(_MaxJdk)' == '' ">$(JI_MAX_JDK)</_MaxJdk>
18-
<JdksRoot Condition=" '$(JdksRoot)' == '' And '$(JAVA_HOME_11_X64)' != '' And Exists($(JAVA_HOME_11_X64)) ">$(JAVA_HOME_11_X64)</JdksRoot>
1918
<JdksRoot Condition=" '$(JdksRoot)' == '' And '$(JAVA_HOME_17_X64)' != '' And Exists($(JAVA_HOME_17_X64)) ">$(JAVA_HOME_17_X64)</JdksRoot>
2019
</PropertyGroup>
2120
<JdkInfo
2221
JdksRoot="$(JdksRoot)"
2322
MakeFragmentFile="$(MSBuildThisFileDirectory)..\..\bin\Build$(Configuration)\JdkInfo.mk"
24-
MinimumJdkVersion="11"
23+
MinimumJdkVersion="17"
2524
MaximumJdkVersion="$(_MaxJdk)"
2625
DotnetToolPath="$(DotnetToolPath)"
2726
PropertyFile="$(_TopDir)\bin\Build$(Configuration)\JdkInfo.props">
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.gradle
2+
android.xml.fixed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>$(DotNetTargetFramework)</TargetFramework>
5+
</PropertyGroup>
6+
7+
<Import Project="..\..\TargetFrameworkDependentValues.props" />
8+
9+
<PropertyGroup>
10+
<RootNamespace>Java.Interop.Samples.NativeAotFromAndroid</RootNamespace>
11+
<ImplicitUsings>enable</ImplicitUsings>
12+
<Nullable>enable</Nullable>
13+
<PublishAot>true</PublishAot>
14+
<PublishAotUsingRuntimePack>true</PublishAotUsingRuntimePack>
15+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
16+
<NativeLib>Shared</NativeLib>
17+
<RuntimeIdentifier Condition=" '$(RuntimeIdentifier)' == '' ">linux-bionic-arm64</RuntimeIdentifier>
18+
<!-- Needed for cross-compilation, e.g. build linux-bionic-arm64 from osx-x64 -->
19+
<PlatformTarget>AnyCPU</PlatformTarget>
20+
</PropertyGroup>
21+
22+
<ItemGroup>
23+
<!-- https://github.com/exelix11/SysDVR/blob/master/Client/Client.csproj -->
24+
<!-- Android needs a proper soname property or it will refuse to load the library -->
25+
<LinkerArg Include="-Wl,-soname,lib$(AssemblyName)$(NativeBinaryExt)" />
26+
<TrimmerRootAssembly Include="Hello-NativeAOTFromAndroid" />
27+
</ItemGroup>
28+
29+
<ItemGroup>
30+
<ProjectReference Include="..\..\src\Java.Interop\Java.Interop.csproj" />
31+
<ProjectReference Include="..\..\src\Java.Runtime.Environment\Java.Runtime.Environment.csproj" />
32+
<ProjectReference Include="..\..\src\Java.Base\Java.Base.csproj" />
33+
<ProjectReference Include="..\..\src\Java.Interop.Export\Java.Interop.Export.csproj" />
34+
<ProjectReference
35+
Include="..\..\tools\jcw-gen\jcw-gen.csproj"
36+
ReferenceOutputAssembly="false"
37+
/>
38+
<ProjectReference
39+
Include="..\..\tools\jnimarshalmethod-gen\Xamarin.Android.Tools.JniMarshalMethodGenerator.csproj"
40+
ReferenceOutputAssembly="false"
41+
/>
42+
<ProjectReference
43+
Include="..\..\tools\generator\generator.csproj"
44+
ReferenceOutputAssembly="false"
45+
/>
46+
</ItemGroup>
47+
48+
<Import Project="Hello-NativeAOTFromAndroid.targets" />
49+
</Project>
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
<Project>
2+
3+
<UsingTask AssemblyFile="$(MSBuildThisFileDirectory)..\..\bin\Build$(Configuration)\Java.Interop.BootstrapTasks.dll" TaskName="Java.Interop.BootstrapTasks.ParseAndroidResources" />
4+
5+
<PropertyGroup>
6+
<GeneratorPath>$(UtilityOutputFullPath)generator.dll</GeneratorPath>
7+
<_JcwOutputDir>app/src/main/java/my/</_JcwOutputDir>
8+
<_GradleJniLibsDir>app/src/main/jniLibs/arm64-v8a</_GradleJniLibsDir>
9+
</PropertyGroup>
10+
11+
<PropertyGroup>
12+
<_NdkSysrootAbi Condition=" '$(RuntimeIdentifier)' == 'linux-bionic-arm64' ">aarch64-linux-android</_NdkSysrootAbi>
13+
<_NdkClangPrefix Condition=" '$(RuntimeIdentifier)' == 'linux-bionic-arm64' ">aarch64-linux-android21-</_NdkClangPrefix>
14+
<_NdkSysrootAbi Condition=" '$(RuntimeIdentifier)' == 'linux-bionic-x64' ">x86_64-linux-android</_NdkSysrootAbi>
15+
<_NdkClangPrefix Condition=" '$(RuntimeIdentifier)' == 'linux-bionic-x64' ">x86_64-linux-android21-</_NdkClangPrefix>
16+
<_NdkPrebuiltAbi Condition=" '$(NETCoreSdkRuntimeIdentifier)' == 'osx-x64' ">darwin-x86_64</_NdkPrebuiltAbi>
17+
<_NdkSysrootLibDir>$(ANDROID_NDK_HOME)/toolchains/llvm/prebuilt/$(_NdkPrebuiltAbi)/sysroot/usr/lib/$(_NdkSysrootAbi)</_NdkSysrootLibDir>
18+
<_NdkBinDir>$(ANDROID_NDK_HOME)/toolchains/llvm/prebuilt/$(_NdkPrebuiltAbi)/bin</_NdkBinDir>
19+
</PropertyGroup>
20+
21+
<PropertyGroup Condition="$(RuntimeIdentifier.StartsWith('linux-bionic'))">
22+
<CppCompilerAndLinker>$(_NdkBinDir)/$(_NdkClangPrefix)clang</CppCompilerAndLinker>
23+
<ObjCopyName>$(_NdkBinDir)/llvm-objcopy</ObjCopyName>
24+
</PropertyGroup>
25+
26+
<ItemGroup Condition="$(RuntimeIdentifier.StartsWith('linux-bionic'))">
27+
<LinkerArg Include="-Wl,--undefined-version" />
28+
</ItemGroup>
29+
30+
<Target Name="_ValidateNdkEnvironment"
31+
BeforeTargets="Build">
32+
<Error
33+
Condition=" '$(ANDROID_NDK_HOME)' == '' Or !Exists($(ANDROID_NDK_HOME)) "
34+
Text="Set the %24ANDROID_NDK_HOME environment variable to the path of the Android NDK."
35+
/>
36+
<Error
37+
Condition=" !Exists($(_NdkSysrootLibDir))"
38+
Text="NDK 'sysroot' dir `$(_NdkSysrootLibDir)` does not exist. You're on your own."
39+
/>
40+
</Target>
41+
42+
<ItemGroup>
43+
<_GenerateAndroidBindingInputs Include="$(GeneratorPath)" />
44+
<_GenerateAndroidBindingInputs Include="$(MSBuildThisFileFullPath)" />
45+
<_GenerateAndroidBindingInputs Include="Transforms\**" />
46+
<_GenerateAndroidBindingInputs Include="$(IntermediateOutputPath)mcw\api.xml" />
47+
</ItemGroup>
48+
49+
<Target Name="_GenerateAndroidBinding"
50+
BeforeTargets="CoreCompile"
51+
Inputs="@(_GenerateAndroidBindingInputs)"
52+
Outputs="$(IntermediateOutputPath)mcw\Hello-NativeAOTFromAndroid.projitems">
53+
<MakeDir Directories="$(IntermediateOutputPath)mcw" />
54+
<PropertyGroup>
55+
<Generator>"$(GeneratorPath)"</Generator>
56+
<_GenFlags>--public --global</_GenFlags>
57+
<_Out>-o "$(IntermediateOutputPath)mcw"</_Out>
58+
<_Codegen>--codegen-target=JavaInterop1</_Codegen>
59+
<_Fixup>--fixup=Transforms/Metadata.xml</_Fixup>
60+
<_Enums1>--preserve-enums --enumflags=Transforms/enumflags --enumfields=Transforms/map.csv --enummethods=Transforms/methodmap.csv</_Enums1>
61+
<_Enums2>--enummetadata=$(IntermediateOutputPath)mcw/enummetadata</_Enums2>
62+
<_Assembly>"--assembly=$(AssemblyName)"</_Assembly>
63+
<_TypeMap>--type-map-report=$(IntermediateOutputPath)mcw/type-mapping.txt</_TypeMap>
64+
<_Api>android.xml</_Api>
65+
<_Dirs>--enumdir=$(IntermediateOutputPath)mcw</_Dirs>
66+
<_FullIntermediateOutputPath>$([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)'))</_FullIntermediateOutputPath>
67+
<_LangFeatures>--lang-features=nullable-reference-types,default-interface-methods,nested-interface-types,interface-constants</_LangFeatures>
68+
</PropertyGroup>
69+
<ItemGroup>
70+
<_RefAsmDir Include="@(ReferencePathWithRefAssemblies->'%(RootDir)%(Directory).'->Distinct())" />
71+
<_Lib Include="@(_RefAsmDir->'-L &quot;%(Identity)&quot;')" />
72+
<_JavaBaseRef Include="@(ReferencePathWithRefAssemblies)"
73+
Condition=" '%(FileName)' == 'Java.Base' "
74+
/>
75+
<_Ref Include="@(_JavaBaseRef->'-r &quot;%(FullPath)&quot;')" />
76+
</ItemGroup>
77+
<ItemGroup>
78+
<!-- I can't find a good way to trim the trailing `\`, so append with `.` so we can sanely quote for $(_Libpath) -->
79+
</ItemGroup>
80+
<Exec
81+
Command="$(DotnetToolPath) $(Generator) $(_GenFlags) $(_ApiLevel) $(_Out) @(_Lib, ' ') @(_Ref, ' ') $(_Codegen) $(_Fixup) $(_Enums1) $(_Enums2) $(_Versions) $(_Annotations) $(_Assembly) $(_TypeMap) $(_LangFeatures) $(_Dirs) $(_Api) $(_WithJavadocXml)"
82+
IgnoreStandardErrorWarningFormat="True"
83+
/>
84+
<ItemGroup>
85+
<Compile Include="$(_FullIntermediateOutputPath)\mcw\**\*.cs" KeepDuplicates="False" />
86+
</ItemGroup>
87+
<XmlPeek
88+
Namespaces="&lt;Namespace Prefix='msbuild' Uri='http://schemas.microsoft.com/developer/msbuild/2003' /&gt;"
89+
XmlInputPath="$(IntermediateOutputPath)mcw\Hello-NativeAOTFromAndroid.projitems"
90+
Query="/msbuild:Project/msbuild:PropertyGroup/msbuild:DefineConstants/text()" >
91+
<Output TaskParameter="Result" PropertyName="_GeneratedDefineConstants" />
92+
</XmlPeek>
93+
<PropertyGroup>
94+
<DefineConstants>$(DefineConstants);$([System.String]::Copy('$(_GeneratedDefineConstants)').Replace ('%24(DefineConstants);', ''))</DefineConstants>
95+
</PropertyGroup>
96+
</Target>
97+
98+
<Target Name="_CreateJavaCallableWrappers"
99+
Condition=" '$(TargetPath)' != '' "
100+
BeforeTargets="_BuildAppApk"
101+
Inputs="$(TargetPath)"
102+
Outputs="$(_JcwOutputDir).stamp">
103+
<RemoveDir Directories="$(_JcwOutputDir)" />
104+
<MakeDir Directories="$(_JcwOutputDir)" />
105+
<ItemGroup>
106+
<!-- I can't find a good way to trim the trailing `\`, so append with `.` so we can sanely quote for $(_Libpath) -->
107+
<_JcwGenRefAsmDirs Include="@(ReferencePathWithRefAssemblies->'%(RootDir)%(Directory).'->Distinct())" />
108+
</ItemGroup>
109+
<PropertyGroup>
110+
<_JcwGen>"$(UtilityOutputFullPath)/jcw-gen.dll"</_JcwGen>
111+
<_Target>--codegen-target JavaInterop1</_Target>
112+
<_Output>-o "$(_JcwOutputDir)"</_Output>
113+
<_Libpath>@(_JcwGenRefAsmDirs->'-L "%(Identity)"', ' ')</_Libpath>
114+
</PropertyGroup>
115+
<Exec Command="$(DotnetToolPath) $(_JcwGen) &quot;$(TargetPath)&quot; $(_Target) $(_Output) $(_Libpath)" />
116+
<Touch Files="$(_JcwOutputDir).stamp" AlwaysCreate="True" />
117+
</Target>
118+
119+
<Target Name="_AddMarshalMethods"
120+
Condition=" '$(TargetPath)' != '' "
121+
Inputs="$(TargetPath)"
122+
Outputs="$(IntermediateOutputPath).added-marshal-methods"
123+
AfterTargets="_CreateJavaCallableWrappers">
124+
<ItemGroup>
125+
<!-- I can't find a good way to trim the trailing `\`, so append with `.` so we can sanely quote for $(_Libpath) -->
126+
<_JnimmRefAsmDirs Include="@(RuntimePackAsset->'%(RootDir)%(Directory).'->Distinct())" />
127+
</ItemGroup>
128+
<PropertyGroup>
129+
<_JnimarshalmethodGen>"$(UtilityOutputFullPath)/jnimarshalmethod-gen.dll"</_JnimarshalmethodGen>
130+
<_Verbosity>-v -v --keeptemp</_Verbosity>
131+
<_Libpath>-L "$(TargetDir)" @(_JnimmRefAsmDirs->'-L "%(Identity)"', ' ')</_Libpath>
132+
<!-- <_Output>-o "$(IntermediateOutputPath)/jonp"</_Output> -->
133+
</PropertyGroup>
134+
135+
<Exec Command="$(DotnetToolPath) $(_JnimarshalmethodGen) &quot;$(TargetPath)&quot; $(_Verbosity) $(_Libpath)" />
136+
137+
<!-- the IlcCompile target uses files from `$(IntermediateOutputPath)`, not `$(TargetPath)`, so… update both? -->
138+
<Copy SourceFiles="$(TargetPath)" DestinationFolder="$(IntermediateOutputPath)" />
139+
140+
<Touch Files="$(IntermediateOutputPath).added-marshal-methods" AlwaysCreate="True" />
141+
</Target>
142+
143+
<ItemGroup>
144+
<_BuildAppApkInput Include="$(MSBuildThisFileFullPath)" />
145+
<_BuildAppApkInput Include="app\src\main\java\**\*.java" />
146+
<_BuildAppApkInput Include="app\src\main\AndroidManifest.xml" />
147+
<_BuildAppApkInput Include="app\**\build.gradle" />
148+
<_BuildAppApkInput Include="$(NativeBinary)" />
149+
<_BuildAppApkInput Include="$(OutputPath)java-interop.jar" />
150+
</ItemGroup>
151+
152+
<PropertyGroup>
153+
<_AfterBuildDependsOnTargets>
154+
_CreateJavaCallableWrappers;
155+
_AddMarshalMethods;
156+
</_AfterBuildDependsOnTargets>
157+
</PropertyGroup>
158+
159+
<Target Name="_AfterBuild"
160+
AfterTargets="Build"
161+
DependsOnTargets="$(_AfterBuildDependsOnTargets)"
162+
/>
163+
164+
<PropertyGroup>
165+
<_GradleRtxtPath>app\build\intermediates\runtime_symbol_list\release\R.txt</_GradleRtxtPath>
166+
</PropertyGroup>
167+
168+
<Target Name="_BuildRtxt"
169+
BeforeTargets="CoreCompile"
170+
Inputs="@(_BuildAppApkInput)"
171+
Outputs="$(_GradleRtxtPath);$(IntermediateOutputPath)R.g.cs">
172+
<Exec
173+
Command="&quot;$(GradleWPath)&quot; $(GradleArgs) :app:processReleaseResources"
174+
EnvironmentVariables="JAVA_HOME=$(JavaSdkDirectory);APP_HOME=$(GradleHome)"
175+
WorkingDirectory="$(MSBuildThisFileDirectory)"
176+
/>
177+
<ParseAndroidResources
178+
AndroidResourceFile="$(_GradleRtxtPath)"
179+
OutputFile="$(IntermediateOutputPath)R.g.cs"
180+
DeclaringNamespaceName="$(RootNamespace)"
181+
DeclaringClassName="R"
182+
/>
183+
<ItemGroup>
184+
<FileWrites Include="$(IntermediateOutputPath)R.g.cs" />
185+
<Compile Include="$(IntermediateOutputPath)R.g.cs" />
186+
</ItemGroup>
187+
</Target>
188+
189+
<Target Name="_BuildAppApk"
190+
AfterTargets="Publish"
191+
Inputs="@(_BuildAppApkInput)"
192+
Outputs="app/build/outputs/apk/release/app-release.apk">
193+
<Error
194+
Condition=" '$(ANDROID_HOME)' == '' "
195+
Message="You MUST set the ANDROID_HOME environment variable to the path to your Android SDK installation."
196+
/>
197+
<MakeDir Directories="$(_GradleJniLibsDir);app/lib" />
198+
<ItemGroup>
199+
<_GradleBuildSource Include="$(NativeBinary)" />
200+
<_GradleBuildTarget Include="$(_GradleJniLibsDir)\lib$(AssemblyName)$(NativeBinaryExt)" />
201+
202+
<_GradleBuildSource Include="$(OutputPath)java-interop.jar" />
203+
<_GradleBuildTarget Include="app\lib\java-interop.jar" />
204+
</ItemGroup>
205+
<Copy
206+
SourceFiles="@(_GradleBuildSource)"
207+
DestinationFiles="@(_GradleBuildTarget)"
208+
/>
209+
<Exec
210+
Command="&quot;$(GradleWPath)&quot; $(GradleArgs) -d assembleRelease > gradle.log"
211+
EnvironmentVariables="JAVA_HOME=$(JavaSdkDirectory);APP_HOME=$(GradleHome)"
212+
WorkingDirectory="$(MSBuildThisFileDirectory)"
213+
/>
214+
<Copy
215+
SourceFiles="app/build/outputs/apk/release/app-release.apk"
216+
DestinationFiles="$(OutputPath)net.dot.jni.helloandroid-Signed.apk"
217+
/>
218+
</Target>
219+
</Project>

0 commit comments

Comments
 (0)