Skip to content

Commit

Permalink
[Android] Make AndroidAppBuilder "dotnet build" friendly (dotnet#42597)
Browse files Browse the repository at this point in the history
  • Loading branch information
EgorBo authored Sep 29, 2020
1 parent d062c63 commit 73e1543
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 41 deletions.
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@

<PropertyGroup Condition="'$(TargetsMobile)' == 'true'">
<AppleAppBuilderDir>$([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'AppleAppBuilder', 'Debug', '$(NetCoreAppCurrent)'))</AppleAppBuilderDir>
<AndroidAppBuilderDir>$([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'AndroidAppBuilder', 'Debug', '$(NetCoreAppCurrent)'))</AndroidAppBuilderDir>
<AndroidAppBuilderDir>$([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'AndroidAppBuilder', 'Debug', '$(NetCoreAppCurrent)', 'publish'))</AndroidAppBuilderDir>
<WasmAppBuilderDir>$([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'WasmAppBuilder', 'Debug', '$(NetCoreAppCurrent)', 'publish'))</WasmAppBuilderDir>
<WasmBuildTasksDir>$([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'WasmBuildTasks', 'Debug', '$(NetCoreAppCurrent)', 'publish'))</WasmBuildTasksDir>
<MonoAOTCompilerDir>$([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'MonoAOTCompiler', 'Debug', '$(NetCoreAppCurrent)'))</MonoAOTCompilerDir>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,72 +1,75 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk" DefaultTargets="BuildApp">
<PropertyGroup>
<OutputType>Exe</OutputType>
<OutputPath>bin</OutputPath>
<DebugType>Portable</DebugType>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
<MicrosoftNetCoreAppRuntimePackDir>$(ArtifactsBinDir)microsoft.netcore.app.runtime.android-$(TargetArchitecture)\$(Configuration)\runtimes\android-$(TargetArchitecture)</MicrosoftNetCoreAppRuntimePackDir>
<AndroidAppBuilderDir>$(ArtifactsBinDir)AndroidAppBuilder\$(Configuration)\$(NetCoreAppCurrent)</AndroidAppBuilderDir>
<EnableTargetingPackDownload>false</EnableTargetingPackDownload>
<PublishTrimmed>true</PublishTrimmed>
<_TrimmerDefaultAction>link</_TrimmerDefaultAction>
<InvariantGlobalization>true</InvariantGlobalization> <!-- workaround for https://github.com/dotnet/runtime/issues/41866 -->
<TargetArchitecture Condition="'$(TargetArchitecture)'==''">x64</TargetArchitecture>
<TargetOS>Android</TargetOS>
<MicrosoftNetCoreAppRuntimePackDir>$(ArtifactsBinDir)microsoft.netcore.app.runtime.android-$(TargetArchitecture)\$(Configuration)\runtimes\android-$(TargetArchitecture)\</MicrosoftNetCoreAppRuntimePackDir>
<NoWarn>$(NoWarn),CA1050</NoWarn>
</PropertyGroup>

<!-- Redirect 'dotnet publish' to in-tree runtime pack -->
<Target Name="TrickRuntimePackLocation" AfterTargets="ProcessFrameworkReferences">
<Target Name="RebuildAndroidAppBuilder">
<ItemGroup>
<RuntimePack>
<PackageDirectory>$(ArtifactsBinDir)microsoft.netcore.app.runtime.android-$(TargetArchitecture)\$(Configuration)</PackageDirectory>
</RuntimePack>
<AndroidAppBuilderProject Include="$(RepoTasksDir)mobile.tasks\AndroidAppBuilder\AndroidAppBuilder.csproj" />
</ItemGroup>
<Message Text="Packaged ID: %(RuntimePack.PackageDirectory)" Importance="high" />
<MSBuild Projects="@(AndroidAppBuilderProject)"
Properties="Configuration=$(Configuration);MSBuildRestoreSessionId=$([System.Guid]::NewGuid())"
Targets="Restore"/>
<MSBuild Projects="@(AndroidAppBuilderProject)"
Properties="Configuration=$(Configuration)"
Targets="Build;Publish"/>
</Target>

<UsingTask TaskName="AndroidAppBuilderTask"
AssemblyFile="$(AndroidAppBuilderDir)\AndroidAppBuilder.dll" />
<UsingTask TaskName="AndroidAppBuilderTask" AssemblyFile="$(AndroidAppBuilderTasksAssemblyPath)"/>

<!-- Build APK during 'dotnet publish' -->
<Target Name="BuildAppBundle" AfterTargets="CopyFilesToPublishDirectory">
<Target Name="BuildApp" DependsOnTargets="RebuildAndroidAppBuilder;Build">
<PropertyGroup>
<AndroidAbi Condition="'$(Platform)'=='arm64'">arm64-v8a</AndroidAbi>
<AndroidAbi Condition="'$(Platform)'=='arm'">armeabi-v7a</AndroidAbi>
<AndroidAbi Condition="'$(Platform)'=='x64'">x86_64</AndroidAbi>
<AndroidAbi Condition="'$(AndroidAbi)'==''">$(Platform)</AndroidAbi>
<ApkDir>$(MSBuildThisFileDirectory)$(PublishDir)\apk</ApkDir>
<StripDebugSymbols>False</StripDebugSymbols>
<StripDebugSymbols Condition="'$(Configuration)' == 'Release'">True</StripDebugSymbols>
<AdbTool>$(ANDROID_SDK_ROOT)\platform-tools\adb</AdbTool>
<ApkDir>$(OutputPath)apk</ApkDir>
</PropertyGroup>

<RemoveDir Directories="$(ApkDir)" />
<ItemGroup>
<AssemblySearchPaths Include="bin" />
<AssemblySearchPaths Include="$(MicrosoftNetCoreAppRuntimePackDir)native"/>
<AssemblySearchPaths Include="$(MicrosoftNetCoreAppRuntimePackDir)lib\$(NetCoreAppCurrent)"/>
</ItemGroup>

<!-- These native libs are not needed for HelloWorld
and can be deleted to save 0.4 Mb for APK size -->
<Delete Files="$(PublishDir)\libSystem.IO.Compression.Native.so" />
<Delete Files="$(PublishDir)\libSystem.Security.Cryptography.Native.OpenSsl.so" />
<RemoveDir Directories="$(ApkDir)" />

<AndroidAppBuilderTask
SourceDir="$(OutputPath)"
Abi="$(AndroidAbi)"
ProjectName="HelloAndroid"
MonoRuntimeHeaders="%(ResolvedRuntimePack.PackageDirectory)\runtimes\android-$(Platform)\native\include\mono-2.0"
MainLibraryFileName="Program.dll"
MonoRuntimeHeaders="$(MicrosoftNetCoreAppRuntimePackDir)\native\include\mono-2.0"
MainLibraryFileName="$(AssemblyName).dll"
StripDebugSymbols="$(StripDebugSymbols)"
SourceDir="$(MSBuildThisFileDirectory)$(PublishDir)"
AssemblySearchPaths="@(AssemblySearchPaths)"
OutputDir="$(ApkDir)">
<Output TaskParameter="ApkBundlePath" PropertyName="ApkBundlePath" />
<Output TaskParameter="ApkPackageId" PropertyName="ApkPackageId" />
</AndroidAppBuilderTask>

<Message Importance="High" Text="Apk: $(ApkBundlePath)"/>
<Message Importance="High" Text="PackageId: $(ApkPackageId)"/>

<Exec Condition="'$(DeployAndRun)' == 'true'" Command="$(AdbTool) kill-server"/>
<Exec Condition="'$(DeployAndRun)' == 'true'" Command="$(AdbTool) start-server"/>
<Exec Condition="'$(DeployAndRun)' == 'true'" Command="$(AdbTool) logcat -c" ContinueOnError="WarnAndContinue" />
<Message Condition="'$(DeployAndRun)' == 'true'" Importance="High" Text="Uninstalling app if it exists (throws an error if it doesn't but it can be ignored):"/>
<Message Condition="'$(DeployAndRun)' == 'true'" Importance="High" Text="Uninstalling apk (ignore errors if any):"/>
<Exec Condition="'$(DeployAndRun)' == 'true'" Command="$(AdbTool) uninstall net.dot.HelloAndroid" ContinueOnError="WarnAndContinue" />
<Exec Condition="'$(DeployAndRun)' == 'true'" Command="$(AdbTool) install $(ApkDir)/bin/HelloAndroid.apk" />
<Exec Condition="'$(DeployAndRun)' == 'true'" Command="$(AdbTool) shell am instrument -w net.dot.HelloAndroid/net.dot.MonoRunner" />
<Exec Condition="'$(DeployAndRun)' == 'true'" Command="$(AdbTool) logcat -d -s DOTNET" />
</Target>

<ItemGroup>
<Compile Include="Program.cs" />
</ItemGroup>
</Project>
14 changes: 7 additions & 7 deletions src/mono/netcore/sample/Android/Makefile
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
MONO_CONFIG=Release
MONO_ARCH=arm64
MONO_ARCH=x64
DOTNET := ../../../../.././dotnet.sh

all: runtimepack run

appbuilder:
$(DOTNET) build -c Release ../../../../../tools-local/tasks/mobile.tasks/AndroidAppBuilder/AndroidAppBuilder.csproj

runtimepack:
../../../../.././build.sh Mono+Libs -os Android -arch $(MONO_ARCH) -c $(MONO_CONFIG)

run: clean appbuilder
$(DOTNET) publish -c $(MONO_CONFIG) -r android-$(MONO_ARCH) \
/p:TargetArchitecture=$(MONO_ARCH) /p:DeployAndRun=true
run:
$(DOTNET) build \
/p:TargetArchitecture=$(MONO_ARCH) \
/p:TargetOS=Android \
/p:Configuration=$(MONO_CONFIG) \
/p:DeployAndRun=true

clean:
rm -rf bin
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.IO;
using System.Linq;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

Expand All @@ -11,6 +12,10 @@ public class AndroidAppBuilderTask : Task
[Required]
public string SourceDir { get; set; } = ""!;

public ITaskItem[]? AssemblySearchPaths { get; set; }

public ITaskItem[]? ExtraAssemblies { get; set; }

[Required]
public string MonoRuntimeHeaders { get; set; } = ""!;

Expand Down Expand Up @@ -61,6 +66,8 @@ public override bool Execute()
apkBuilder.BuildApiLevel = BuildApiLevel;
apkBuilder.BuildToolsVersion = BuildToolsVersion;
apkBuilder.StripDebugSymbols = StripDebugSymbols;
apkBuilder.AssemblySearchPaths = AssemblySearchPaths?.Select(a => a.ItemSpec)?.ToArray();
apkBuilder.ExtraAssemblies = ExtraAssemblies?.Select(a => a.ItemSpec)?.ToArray();
(ApkBundlePath, ApkPackageId) = apkBuilder.BuildApk(SourceDir, Abi, MainLibraryFileName, MonoRuntimeHeaders);

return true;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(NetCoreAppCurrent)</TargetFrameworks>
<TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
<OutputType>Library</OutputType>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
Expand All @@ -15,10 +15,16 @@
<PackageReference Include="Microsoft.Build.Framework" Version="$(RefOnlyMicrosoftBuildFrameworkVersion)" />
<PackageReference Include="Microsoft.Build.Tasks.Core" Version="$(RefOnlyMicrosoftBuildTasksCoreVersion)" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="$(RefOnlyMicrosoftBuildUtilitiesCoreVersion)" />
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="4.7.1" />
</ItemGroup>
<ItemGroup>
<Compile Include="ApkBuilder.cs" />
<Compile Include="AndroidAppBuilder.cs" />
<Compile Include="AssemblyResolver.cs" />
<Compile Include="Utils.cs" />
</ItemGroup>

<Target Name="PublishBuilder"
AfterTargets="Build"
DependsOnTargets="Publish" />
</Project>
39 changes: 36 additions & 3 deletions tools-local/tasks/mobile.tasks/AndroidAppBuilder/ApkBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ public class ApkBuilder
public string? BuildToolsVersion { get; set; }
public string? OutputDir { get; set; }
public bool StripDebugSymbols { get; set; }
public string[]? AssemblySearchPaths { get; set; }
public string[]? ExtraAssemblies { get; set; }

public (string apk, string packageId) BuildApk(
string sourceDir, string abi, string entryPointLib, string monoRuntimeHeaders)
Expand All @@ -31,7 +33,8 @@ public class ApkBuilder
if (string.IsNullOrEmpty(entryPointLib))
throw new ArgumentException("entryPointLib shouldn't be empty");

if (!File.Exists(Path.Combine(sourceDir, entryPointLib)))
string entryPointLibPath = Path.Combine(sourceDir, entryPointLib);
if (!File.Exists(entryPointLibPath))
throw new ArgumentException($"{entryPointLib} was not found in sourceDir='{sourceDir}'");

if (string.IsNullOrEmpty(ProjectName))
Expand Down Expand Up @@ -93,7 +96,38 @@ public class ApkBuilder
extensionsToIgnore.Add(".dbg");
}

// Copy AppDir to OutputDir/assets-tozip (ignore native files)
var assembliesToResolve = new List<string> { entryPointLibPath };
if (ExtraAssemblies != null)
assembliesToResolve.AddRange(ExtraAssemblies);

// try to resolve dependencies of entryPointLib + ExtraAssemblies from AssemblySearchPaths
// and copy them to sourceDir
if (AssemblySearchPaths?.Length > 0)
{
string[] resolvedDependencies = AssemblyResolver.ResolveDependencies(assembliesToResolve.ToArray(), AssemblySearchPaths, true);
foreach (string resolvedDependency in resolvedDependencies)
{
string destination = Path.Combine(sourceDir, Path.GetFileName(resolvedDependency));
if (!File.Exists(destination))
File.Copy(resolvedDependency, destination);
}
}
else
{
AssemblySearchPaths = new[] {OutputDir};
}

// copy all native libs from AssemblySearchPaths to sourceDir
// TODO: skip some if not used by the app
string[] allFiles = AssemblySearchPaths.SelectMany(p => Directory.GetFiles(p, "*", SearchOption.AllDirectories)).ToArray();
foreach (string nativeLib in allFiles.Where(f => f.EndsWith(".a") || f.EndsWith(".so")))
{
string destination = Path.Combine(sourceDir, Path.GetFileName(nativeLib));
if (!File.Exists(destination))
File.Copy(nativeLib, destination);
}

// Copy sourceDir to OutputDir/assets-tozip (ignore native files)
// these files then will be zipped and copied to apk/assets/assets.zip
Utils.DirectoryCopy(sourceDir, Path.Combine(OutputDir, "assets-tozip"), file =>
{
Expand Down Expand Up @@ -195,7 +229,6 @@ public class ApkBuilder
Utils.RunProcess(aapt, $"package -f -m -F {apkFile} -A assets -M AndroidManifest.xml -I {androidJar}", workingDir: OutputDir);

var dynamicLibs = new List<string>();
dynamicLibs.Add(Path.Combine(OutputDir, "monodroid", "libmonodroid.so"));
dynamicLibs.AddRange(Directory.GetFiles(sourceDir, "*.so"));

// add all *.so files to lib/%abi%/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;

internal class AssemblyResolver : MetadataAssemblyResolver
{
private string[] _searchPaths;

public AssemblyResolver(string[] searchPaths) => _searchPaths = searchPaths;

public static string[] ResolveDependencies(
string[] assembliesToResolve, string[] searchPaths, bool ignoreErrors = true)
{
var assemblies = new Dictionary<string, Assembly>();
var mlc = new MetadataLoadContext(new AssemblyResolver(searchPaths), "System.Private.CoreLib");
foreach (string assemblyPath in assembliesToResolve)
{
try
{
AddAssembly(mlc, mlc.LoadFromAssemblyPath(assemblyPath), assemblies, ignoreErrors);
}
catch (Exception)
{
if (!ignoreErrors)
{
throw;
}
}
}
return assemblies.Values.Select(i => i.Location).Distinct().ToArray();
}

private static void AddAssembly(MetadataLoadContext mlc, Assembly assembly, Dictionary<string, Assembly> assemblies, bool ignoreErrors)
{
if (assemblies.ContainsKey(assembly.GetName().Name!))
return;
assemblies[assembly.GetName().Name!] = assembly;
foreach (AssemblyName name in assembly.GetReferencedAssemblies())
{
try
{
Assembly refAssembly = mlc.LoadFromAssemblyName(name);
AddAssembly(mlc, refAssembly, assemblies, ignoreErrors);
}
catch (Exception)
{
if (!ignoreErrors)
{
throw;
}
}
}
}

public override Assembly? Resolve(MetadataLoadContext context, AssemblyName assemblyName)
{
string name = assemblyName.Name!;
foreach (string dir in _searchPaths)
{
string path = Path.Combine(dir, name + ".dll");
if (File.Exists(path))
return context.LoadFromAssemblyPath(path);
}
return null;
}
}

0 comments on commit 73e1543

Please sign in to comment.