mirror of
https://github.com/VSadov/Satori.git
synced 2025-06-09 17:44:48 +09:00
[mono] Add Android sample and AndroidAppBuilder task (#35483)
This commit is contained in:
parent
11c2e4c3b8
commit
0fa0b905f6
17 changed files with 1042 additions and 2 deletions
|
@ -118,6 +118,57 @@
|
|||
<Error Condition="'$(TestRunExitCode)' != '0'" Text="$(TestRunErrorMessage)" />
|
||||
</Target>
|
||||
|
||||
<!-- Generate a self-contained app bundle for Android with tests.
|
||||
This target is executed once build is done for a test lib (after CopyFilesToOutputDirectory target) -->
|
||||
<UsingTask TaskName="AndroidAppBuilderTask"
|
||||
AssemblyFile="$(ArtifactsObjDir)mono\AndroidAppBuilder\$(TargetArchitecture)\$(Configuration)\AndroidAppBuilder.dll" />
|
||||
<Target Condition="'$(TargetOS)' == 'Android'" Name="BundleTestAndroidApp" AfterTargets="CopyFilesToOutputDirectory">
|
||||
<PropertyGroup>
|
||||
<RuntimePackDir>$(ArtifactsDir)bin\lib-runtime-packs\runtimes\android-$(TargetArchitecture)</RuntimePackDir>
|
||||
<BundleDir>$(OutDir)\Bundle</BundleDir>
|
||||
<AndroidTestRunner>$(RepoRoot)\src\mono\msbuild\AndroidTestRunner\bin</AndroidTestRunner>
|
||||
<AndroidAbi Condition="'$(TargetArchitecture)'=='arm64'">arm64-v8a</AndroidAbi>
|
||||
<AndroidAbi Condition="'$(TargetArchitecture)'=='arm'">armeabi</AndroidAbi>
|
||||
<AndroidAbi Condition="'$(TargetArchitecture)'=='x64'">x86_64</AndroidAbi>
|
||||
<AndroidAbi Condition="'$(AndroidAbi)'==''">$(TargetArchitecture)</AndroidAbi>
|
||||
</PropertyGroup>
|
||||
<!-- TEMP: We need to copy additional stuff into $(OutDir)\Bundle
|
||||
1) The whole BCL
|
||||
2) Test Runner (with xharness client-side lib)
|
||||
-->
|
||||
<ItemGroup>
|
||||
<TestBinaries Include="$(OutDir)\*.*"/>
|
||||
<AndroidTestRunnerBinaries Include="$(AndroidTestRunner)\*.*" />
|
||||
<BclBinaries Include="$(RuntimePackDir)\lib\$(NetCoreAppCurrent)\*.*"
|
||||
Exclude="$(RuntimePackDir)\lib\$(NetCoreAppCurrent)\System.Runtime.WindowsRuntime.dll" />
|
||||
<BclBinaries Include="$(RuntimePackDir)\native\*.*" Exclude="$(RuntimePackDir)\native\libmono.dylib" />
|
||||
|
||||
<!-- remove PDBs and DBGs to save some space until we integrate ILLink -->
|
||||
<BclBinaries Remove="$(RuntimePackDir)\lib\$(NetCoreAppCurrent)\*.pdb" />
|
||||
<BclBinaries Remove="$(RuntimePackDir)\lib\$(NetCoreAppCurrent)\*.dbg" />
|
||||
</ItemGroup>
|
||||
|
||||
<Error Condition="!Exists('$(AndroidTestRunner)')" Text="AndroidTestRunner=$(AndroidTestRunner) doesn't exist" />
|
||||
<Error Condition="!Exists('$(RuntimePackDir)')" Text="RuntimePackDir=$(RuntimePackDir) doesn't exist" />
|
||||
<RemoveDir Directories="$(BundleDir)" />
|
||||
<Copy SourceFiles="@(TestBinaries)" DestinationFolder="$(BundleDir)" SkipUnchangedFiles="true"/>
|
||||
<Copy SourceFiles="@(AndroidTestRunnerBinaries)" DestinationFolder="$(BundleDir)\%(RecursiveDir)" SkipUnchangedFiles="true"/>
|
||||
<Copy SourceFiles="@(BclBinaries)" DestinationFolder="$(BundleDir)\%(RecursiveDir)" SkipUnchangedFiles="true"/>
|
||||
|
||||
<AndroidAppBuilderTask
|
||||
Abi="$(AndroidAbi)"
|
||||
ProjectName="$(AssemblyName)"
|
||||
MonoRuntimeHeaders="$(RuntimePackDir)\native\include\mono-2.0"
|
||||
MainLibraryFileName="AndroidTestRunner.dll"
|
||||
OutputDir="$(BundleDir)"
|
||||
SourceDir="$(BundleDir)">
|
||||
<Output TaskParameter="ApkPackageId" PropertyName="ApkPackageId" />
|
||||
<Output TaskParameter="ApkBundlePath" PropertyName="ApkBundlePath" />
|
||||
</AndroidAppBuilderTask>
|
||||
<Message Importance="High" Text="PackageId: $(ApkPackageId)"/>
|
||||
<Message Importance="High" Text="Apk: $(ApkBundlePath)"/>
|
||||
</Target>
|
||||
|
||||
<!-- Generate a self-contained app bundle for iOS with tests.
|
||||
This target is executed once build is done for a test lib (after CopyFilesToOutputDirectory target) -->
|
||||
<UsingTask TaskName="AppleAppBuilderTask"
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<DefineConstants>$(DefineConstants);XMLSERIALIZERGENERATORTESTS</DefineConstants>
|
||||
<TargetFrameworks>$(NetCoreAppCurrent)</TargetFrameworks>
|
||||
<CoverageSupported>false</CoverageSupported>
|
||||
<SkipTestsOnPlatform Condition="'$(TargetOS)' == 'FreeBSD' or '$(TargetOS)' == 'iOS' or '$(TargetOS)' == 'tvOS' or '$(TargetArchitecture)' == 'arm' or '$(TargetArchitecture)' == 'arm64' or '$(TargetArchitecture)' == 'armel' or '$(TargetArchitecture)' == 'wasm'">true</SkipTestsOnPlatform>
|
||||
<SkipTestsOnPlatform Condition="'$(TargetsMobile)' == 'true' or '$(TargetOS)' == 'FreeBSD' or '$(TargetArchitecture)' == 'arm' or '$(TargetArchitecture)' == 'arm64' or '$(TargetArchitecture)' == 'armel' or '$(TargetArchitecture)' == 'wasm'">true</SkipTestsOnPlatform>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Reuse the same runtimeconfig used by MSBuild. -->
|
||||
|
|
|
@ -936,9 +936,18 @@
|
|||
Targets="Restore;Build" />
|
||||
</Target>
|
||||
|
||||
<Target Name="BuildAndroidAppBuilder">
|
||||
<MSBuild Projects="$(MonoProjectRoot)msbuild\AndroidAppBuilder\AndroidAppBuilder.csproj"
|
||||
Properties="Configuration=$(Configuration)"
|
||||
Targets="Restore;Build" />
|
||||
<MSBuild Projects="$(MonoProjectRoot)msbuild\AndroidTestRunner\AndroidTestRunner.csproj"
|
||||
Properties="Configuration=$(Configuration)"
|
||||
Targets="Restore;Build" />
|
||||
</Target>
|
||||
|
||||
<!-- Ordering matters! Overwriting the Build target. -->
|
||||
<!-- General targets -->
|
||||
<Target Name="Build" DependsOnTargets="BuildMonoRuntimeUnix;BuildMonoRuntimeWindows;BuildAppleAppBuilder">
|
||||
<Target Name="Build" DependsOnTargets="BuildMonoRuntimeUnix;BuildMonoRuntimeWindows;BuildAppleAppBuilder;BuildAndroidAppBuilder">
|
||||
<PropertyGroup>
|
||||
<_MonoRuntimeFilePath Condition="'$(TargetsWindows)' == 'true' and '$(Platform)' == 'x64'">$(MonoObjDir)x64\Bin\$(Configuration)\mono-2.0-sgen.dll</_MonoRuntimeFilePath>
|
||||
<_MonoRuntimeFilePath Condition="'$(TargetsWindows)' == 'true' and '$(Platform)' == 'x86'">$(MonoObjDir)Win32\Bin\$(Configuration)\mono-2.0-sgen.dll</_MonoRuntimeFilePath>
|
||||
|
|
66
src/mono/msbuild/AndroidAppBuilder/AndroidAppBuilder.cs
Normal file
66
src/mono/msbuild/AndroidAppBuilder/AndroidAppBuilder.cs
Normal file
|
@ -0,0 +1,66 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.Build.Framework;
|
||||
using Microsoft.Build.Utilities;
|
||||
|
||||
public class AndroidAppBuilderTask : Task
|
||||
{
|
||||
[Required]
|
||||
public string SourceDir { get; set; } = ""!;
|
||||
|
||||
[Required]
|
||||
public string MonoRuntimeHeaders { get; set; } = ""!;
|
||||
|
||||
/// <summary>
|
||||
/// This library will be used as an entry-point (e.g. TestRunner.dll)
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string MainLibraryFileName { get; set; } = ""!;
|
||||
|
||||
/// <summary>
|
||||
/// Target arch, can be 'x86', 'x86_64', 'armeabi', 'armeabi-v7a' or 'arm64-v8a'
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string Abi { get; set; } = ""!;
|
||||
|
||||
public string? ProjectName { get; set; }
|
||||
|
||||
public string? OutputDir { get; set; }
|
||||
|
||||
public string? AndroidSdk { get; set; }
|
||||
|
||||
public string? AndroidNdk { get; set; }
|
||||
|
||||
public string? MinApiLevel { get; set; }
|
||||
|
||||
public string? BuildApiLevel { get; set; }
|
||||
|
||||
public string? BuildToolsVersion { get; set; }
|
||||
|
||||
[Output]
|
||||
public string ApkBundlePath { get; set; } = ""!;
|
||||
|
||||
[Output]
|
||||
public string ApkPackageId { get; set; } = ""!;
|
||||
|
||||
public override bool Execute()
|
||||
{
|
||||
Utils.Logger = Log;
|
||||
|
||||
var apkBuilder = new ApkBuilder();
|
||||
apkBuilder.ProjectName = ProjectName;
|
||||
apkBuilder.OutputDir = OutputDir;
|
||||
apkBuilder.AndroidSdk = AndroidSdk;
|
||||
apkBuilder.AndroidNdk = AndroidNdk;
|
||||
apkBuilder.MinApiLevel = MinApiLevel;
|
||||
apkBuilder.BuildApiLevel = BuildApiLevel;
|
||||
apkBuilder.BuildToolsVersion = BuildToolsVersion;
|
||||
(ApkBundlePath, ApkPackageId) = apkBuilder.BuildApk(SourceDir, Abi, MainLibraryFileName, MonoRuntimeHeaders);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
24
src/mono/msbuild/AndroidAppBuilder/AndroidAppBuilder.csproj
Normal file
24
src/mono/msbuild/AndroidAppBuilder/AndroidAppBuilder.csproj
Normal file
|
@ -0,0 +1,24 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<OutputPath>bin</OutputPath>
|
||||
<TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Templates\*.*" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Build" Version="$(RefOnlyMicrosoftBuildVersion)" />
|
||||
<PackageReference Include="Microsoft.Build.Framework" Version="$(RefOnlyMicrosoftBuildFrameworkVersion)" />
|
||||
<PackageReference Include="Microsoft.Build.Tasks.Core" Version="$(RefOnlyMicrosoftBuildTasksCoreVersion)" />
|
||||
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="$(RefOnlyMicrosoftBuildUtilitiesCoreVersion)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="ApkBuilder.cs" />
|
||||
<Compile Include="AndroidAppBuilder.cs" />
|
||||
<Compile Include="Utils.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
223
src/mono/msbuild/AndroidAppBuilder/ApkBuilder.cs
Normal file
223
src/mono/msbuild/AndroidAppBuilder/ApkBuilder.cs
Normal file
|
@ -0,0 +1,223 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
public class ApkBuilder
|
||||
{
|
||||
private const string DefaultMinApiLevel = "21";
|
||||
|
||||
public string? ProjectName { get; set; }
|
||||
public string? AndroidNdk { get; set; }
|
||||
public string? AndroidSdk { get; set; }
|
||||
public string? MinApiLevel { get; set; }
|
||||
public string? BuildApiLevel { get; set; }
|
||||
public string? BuildToolsVersion { get; set; }
|
||||
public string? OutputDir { get; set; }
|
||||
|
||||
public (string apk, string packageId) BuildApk(
|
||||
string sourceDir, string abi, string entryPointLib, string monoRuntimeHeaders)
|
||||
{
|
||||
if (!Directory.Exists(sourceDir))
|
||||
throw new ArgumentException($"sourceDir='{sourceDir}' is empty or doesn't exist");
|
||||
|
||||
if (string.IsNullOrEmpty(abi))
|
||||
throw new ArgumentException("abi shoudln't be empty (e.g. x86, x86_64, armeabi, armeabi-v7a or arm64-v8a");
|
||||
|
||||
if (string.IsNullOrEmpty(entryPointLib))
|
||||
throw new ArgumentException("entryPointLib shouldn't be empty");
|
||||
|
||||
if (!File.Exists(Path.Combine(sourceDir, entryPointLib)))
|
||||
throw new ArgumentException($"{entryPointLib} was not found in sourceDir='{sourceDir}'");
|
||||
|
||||
if (string.IsNullOrEmpty(ProjectName))
|
||||
ProjectName = Path.GetFileNameWithoutExtension(entryPointLib);
|
||||
|
||||
if (string.IsNullOrEmpty(OutputDir))
|
||||
OutputDir = Path.Combine(sourceDir, "bin-" + abi);
|
||||
|
||||
if (ProjectName.Contains(' '))
|
||||
throw new ArgumentException($"ProjectName='{ProjectName}' shouldn't not contain spaces.");
|
||||
|
||||
if (string.IsNullOrEmpty(AndroidSdk))
|
||||
AndroidSdk = Environment.GetEnvironmentVariable("ANDROID_SDK_ROOT");
|
||||
|
||||
if (string.IsNullOrEmpty(AndroidNdk))
|
||||
AndroidNdk = Environment.GetEnvironmentVariable("ANDROID_NDK_ROOT");
|
||||
|
||||
if (string.IsNullOrEmpty(AndroidSdk) || !Directory.Exists(AndroidSdk))
|
||||
throw new ArgumentException($"Android SDK='{AndroidSdk}' was not found or empty (can be set via ANDROID_SDK_ROOT envvar).");
|
||||
|
||||
if (string.IsNullOrEmpty(AndroidNdk) || !Directory.Exists(AndroidNdk))
|
||||
throw new ArgumentException($"Android NDK='{AndroidNdk}' was not found or empty (can be set via ANDROID_NDK_ROOT envvar).");
|
||||
|
||||
// Try to get the latest build-tools version if not specified
|
||||
if (string.IsNullOrEmpty(BuildToolsVersion))
|
||||
BuildToolsVersion = GetLatestBuildTools(AndroidSdk);
|
||||
|
||||
// Try to get the latest API level if not specified
|
||||
if (string.IsNullOrEmpty(BuildApiLevel))
|
||||
BuildApiLevel = GetLatestApiLevel(AndroidSdk);
|
||||
|
||||
if (string.IsNullOrEmpty(MinApiLevel))
|
||||
MinApiLevel = DefaultMinApiLevel;
|
||||
|
||||
// make sure BuildApiLevel >= MinApiLevel
|
||||
// only if these api levels are not "preview" (not integers)
|
||||
if (int.TryParse(BuildApiLevel, out int intApi) &&
|
||||
int.TryParse(MinApiLevel, out int intMinApi) &&
|
||||
intApi < intMinApi)
|
||||
{
|
||||
throw new ArgumentException($"BuildApiLevel={BuildApiLevel} <= MinApiLevel={MinApiLevel}. " +
|
||||
"Make sure you've downloaded some recent build-tools in Android SDK");
|
||||
}
|
||||
|
||||
string buildToolsFolder = Path.Combine(AndroidSdk, "build-tools", BuildToolsVersion);
|
||||
if (!Directory.Exists(buildToolsFolder))
|
||||
throw new ArgumentException($"{buildToolsFolder} was not found.");
|
||||
|
||||
Directory.CreateDirectory(OutputDir);
|
||||
Directory.CreateDirectory(Path.Combine(OutputDir, "bin"));
|
||||
Directory.CreateDirectory(Path.Combine(OutputDir, "obj"));
|
||||
Directory.CreateDirectory(Path.Combine(OutputDir, "assets"));
|
||||
|
||||
// Copy AppDir to OutputDir/assets (ignore native files)
|
||||
Utils.DirectoryCopy(sourceDir, Path.Combine(OutputDir, "assets"), file =>
|
||||
{
|
||||
var extension = Path.GetExtension(file);
|
||||
// ignore native files, those go to lib/%abi%
|
||||
if (extension == ".so" || extension == ".a")
|
||||
{
|
||||
// ignore ".pdb" and ".dbg" to make APK smaller
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
// tools:
|
||||
string dx = Path.Combine(buildToolsFolder, "dx");
|
||||
string aapt = Path.Combine(buildToolsFolder, "aapt");
|
||||
string zipalign = Path.Combine(buildToolsFolder, "zipalign");
|
||||
string apksigner = Path.Combine(buildToolsFolder, "apksigner");
|
||||
string androidJar = Path.Combine(AndroidSdk, "platforms", "android-" + BuildApiLevel, "android.jar");
|
||||
string androidToolchain = Path.Combine(AndroidNdk, "build", "cmake", "android.toolchain.cmake");
|
||||
string keytool = "keytool";
|
||||
string javac = "javac";
|
||||
string cmake = "cmake";
|
||||
|
||||
if (!File.Exists(androidJar))
|
||||
throw new ArgumentException($"API level={BuildApiLevel} is not downloaded in Android SDK");
|
||||
|
||||
// 1. Build libruntime-android.so` via cmake
|
||||
|
||||
string monoRuntimeLib = Path.Combine(sourceDir, "libmonosgen-2.0.a");
|
||||
if (!File.Exists(monoRuntimeLib))
|
||||
throw new ArgumentException($"libmonosgen-2.0.a was not found in {sourceDir}");
|
||||
|
||||
string cmakeLists = Utils.GetEmbeddedResource("CMakeLists-android.txt")
|
||||
.Replace("%MonoInclude%", monoRuntimeHeaders)
|
||||
.Replace("%NativeLibrariesToLink%", monoRuntimeLib);
|
||||
File.WriteAllText(Path.Combine(OutputDir, "CMakeLists.txt"), cmakeLists);
|
||||
|
||||
string runtimeAndroidSrc = Utils.GetEmbeddedResource("runtime-android.c")
|
||||
.Replace("%EntryPointLibName%", Path.GetFileName(entryPointLib));
|
||||
File.WriteAllText(Path.Combine(OutputDir, "runtime-android.c"), runtimeAndroidSrc);
|
||||
|
||||
Utils.RunProcess(cmake, workingDir: OutputDir,
|
||||
args: $"-DCMAKE_TOOLCHAIN_FILE={androidToolchain} -DANDROID_ABI=\"{abi}\" -DANDROID_STL=none " +
|
||||
$"-DANDROID_NATIVE_API_LEVEL={MinApiLevel} -B runtime-android");
|
||||
Utils.RunProcess("make", workingDir: Path.Combine(OutputDir, "runtime-android"));
|
||||
|
||||
// 2. Compile Java files
|
||||
|
||||
string javaSrcFolder = Path.Combine(OutputDir, "src", "net", "dot");
|
||||
Directory.CreateDirectory(javaSrcFolder);
|
||||
|
||||
string packageId = $"net.dot.{ProjectName}";
|
||||
|
||||
File.WriteAllText(Path.Combine(javaSrcFolder, "MainActivity.java"),
|
||||
Utils.GetEmbeddedResource("MainActivity.java"));
|
||||
File.WriteAllText(Path.Combine(javaSrcFolder, "MonoRunner.java"),
|
||||
Utils.GetEmbeddedResource("MonoRunner.java"));
|
||||
File.WriteAllText(Path.Combine(OutputDir, "AndroidManifest.xml"),
|
||||
Utils.GetEmbeddedResource("AndroidManifest.xml")
|
||||
.Replace("%PackageName%", packageId)
|
||||
.Replace("%MinSdkLevel%", MinApiLevel));
|
||||
|
||||
string javaCompilerArgs = $"-d obj -classpath src -bootclasspath {androidJar} -source 1.8 -target 1.8 ";
|
||||
Utils.RunProcess(javac, javaCompilerArgs + Path.Combine(javaSrcFolder, "MainActivity.java"), workingDir: OutputDir);
|
||||
Utils.RunProcess(javac, javaCompilerArgs + Path.Combine(javaSrcFolder, "MonoRunner.java"), workingDir: OutputDir);
|
||||
Utils.RunProcess(dx, "--dex --output=classes.dex obj", workingDir: OutputDir);
|
||||
|
||||
// 3. Generate APK
|
||||
|
||||
string apkFile = Path.Combine(OutputDir, "bin", $"{ProjectName}.unaligned.apk");
|
||||
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, "runtime-android", "libruntime-android.so"));
|
||||
dynamicLibs.AddRange(Directory.GetFiles(sourceDir, "*.so"));
|
||||
|
||||
// add all *.so files to lib/%abi%/
|
||||
Directory.CreateDirectory(Path.Combine(OutputDir, "lib", abi));
|
||||
foreach (var dynamicLib in dynamicLibs)
|
||||
{
|
||||
string destRelative = Path.Combine("lib", abi, Path.GetFileName(dynamicLib));
|
||||
File.Copy(dynamicLib, Path.Combine(OutputDir, destRelative), true);
|
||||
Utils.RunProcess(aapt, $"add {apkFile} {destRelative}", workingDir: OutputDir);
|
||||
}
|
||||
Utils.RunProcess(aapt, $"add {apkFile} classes.dex", workingDir: OutputDir);
|
||||
|
||||
// 4. Align APK
|
||||
|
||||
string alignedApk = Path.Combine(OutputDir, "bin", $"{ProjectName}.apk");
|
||||
Utils.RunProcess(zipalign, $"-v 4 {apkFile} {alignedApk}", workingDir: OutputDir);
|
||||
|
||||
// 5. Generate key
|
||||
|
||||
string signingKey = Path.Combine(OutputDir, "debug.keystore");
|
||||
if (!File.Exists(signingKey))
|
||||
{
|
||||
Utils.RunProcess(keytool, "-genkey -v -keystore debug.keystore -storepass android -alias " +
|
||||
"androiddebugkey -keypass android -keyalg RSA -keysize 2048 -noprompt " +
|
||||
"-dname \"CN=Android Debug,O=Android,C=US\"", workingDir: OutputDir, silent: true);
|
||||
}
|
||||
|
||||
// 6. Sign APK
|
||||
|
||||
Utils.RunProcess(apksigner, $"sign --min-sdk-version {MinApiLevel} --ks debug.keystore " +
|
||||
$"--ks-pass pass:android --key-pass pass:android {alignedApk}", workingDir: OutputDir);
|
||||
|
||||
return (alignedApk, packageId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scan android SDK for build tools (ignore preview versions)
|
||||
/// </summary>
|
||||
private static string GetLatestBuildTools(string androidSdkDir)
|
||||
{
|
||||
string? buildTools = Directory.GetDirectories(Path.Combine(androidSdkDir, "build-tools"))
|
||||
.Select(Path.GetFileName)
|
||||
.Where(file => !file!.Contains("-"))
|
||||
.Select(file => Version.TryParse(Path.GetFileName(file), out Version? version) ? version : default)
|
||||
.OrderByDescending(v => v)
|
||||
.FirstOrDefault()?.ToString();
|
||||
|
||||
if (string.IsNullOrEmpty(buildTools))
|
||||
throw new ArgumentException($"Android SDK ({androidSdkDir}) doesn't contain build-tools.");
|
||||
|
||||
return buildTools;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scan android SDK for api levels (ignore preview versions)
|
||||
/// </summary>
|
||||
private static string GetLatestApiLevel(string androidSdkDir)
|
||||
{
|
||||
return Directory.GetDirectories(Path.Combine(androidSdkDir, "platforms"))
|
||||
.Select(file => int.TryParse(Path.GetFileName(file).Replace("android-", ""), out int apiLevel) ? apiLevel : -1)
|
||||
.OrderByDescending(v => v)
|
||||
.FirstOrDefault()
|
||||
.ToString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0"?>
|
||||
<manifest xmlns:a="http://schemas.android.com/apk/res/android"
|
||||
package="%PackageName%"
|
||||
a:versionCode="1"
|
||||
a:versionName="1.0">
|
||||
<uses-sdk a:minSdkVersion="%MinSdkLevel%" />
|
||||
<uses-permission a:name="android.permission.INTERNET"/>
|
||||
<application a:label="%PackageName%"
|
||||
a:largeHeap="true">
|
||||
<activity a:name="net.dot.MainActivity">
|
||||
<intent-filter>
|
||||
<category a:name="android.intent.category.LAUNCHER"/>
|
||||
<action a:name="android.intent.action.MAIN"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
<instrumentation
|
||||
a:name="net.dot.MonoRunner"
|
||||
a:targetPackage="%PackageName%" />
|
||||
</manifest>
|
|
@ -0,0 +1,16 @@
|
|||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
project(runtime-android)
|
||||
|
||||
add_library(
|
||||
runtime-android
|
||||
SHARED
|
||||
runtime-android.c)
|
||||
|
||||
include_directories("%MonoInclude%")
|
||||
|
||||
target_link_libraries(
|
||||
runtime-android
|
||||
%NativeLibrariesToLink%
|
||||
libz.so
|
||||
log)
|
|
@ -0,0 +1,22 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
package net.dot;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
|
||||
public class MainActivity extends Activity
|
||||
{
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this);
|
||||
dlgAlert.setMessage("Use `adb shell am instrument -w " + getApplicationContext().getPackageName() + "net.dot.MonoRunner` to run the tests.");
|
||||
dlgAlert.create().show();
|
||||
}
|
||||
}
|
98
src/mono/msbuild/AndroidAppBuilder/Templates/MonoRunner.java
Normal file
98
src/mono/msbuild/AndroidAppBuilder/Templates/MonoRunner.java
Normal file
|
@ -0,0 +1,98 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
package net.dot;
|
||||
|
||||
import android.app.Instrumentation;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.AssetManager;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class MonoRunner extends Instrumentation
|
||||
{
|
||||
static MonoRunner inst;
|
||||
|
||||
static {
|
||||
System.loadLibrary("runtime-android");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
|
||||
MonoRunner.inst = this;
|
||||
Context context = getContext();
|
||||
AssetManager am = context.getAssets();
|
||||
String filesDir = context.getFilesDir().getAbsolutePath();
|
||||
String cacheDir = context.getCacheDir().getAbsolutePath ();
|
||||
|
||||
copyAssetDir(am, "", filesDir);
|
||||
|
||||
// retcode is what Main() returns in C#
|
||||
int retcode = initRuntime(filesDir, cacheDir);
|
||||
WriteLineToInstrumentation("[Mono] Main() returned " + retcode);
|
||||
runOnMainSync (new Runnable() {
|
||||
public void run() {
|
||||
finish (retcode, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void WriteLineToInstrumentation(String line) {
|
||||
Bundle b = new Bundle();
|
||||
b.putString(Instrumentation.REPORT_KEY_STREAMRESULT, line + "\n");
|
||||
MonoRunner.inst.sendStatus(0, b);
|
||||
}
|
||||
|
||||
static void copyAssetDir(AssetManager am, String path, String outpath) {
|
||||
try {
|
||||
String[] res = am.list(path);
|
||||
for (int i = 0; i < res.length; ++i) {
|
||||
String fromFile = res[i];
|
||||
String toFile = outpath + "/" + res[i];
|
||||
try {
|
||||
InputStream fromStream = am.open(fromFile);
|
||||
Log.w("MONO", "\tCOPYING " + fromFile + " to " + toFile);
|
||||
copy(fromStream, new FileOutputStream(toFile));
|
||||
} catch (FileNotFoundException e) {
|
||||
new File(toFile).mkdirs();
|
||||
copyAssetDir(am, fromFile, toFile);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
Log.w("MONO", "EXCEPTION", e);
|
||||
}
|
||||
}
|
||||
|
||||
static void copy(InputStream in, OutputStream out) throws IOException {
|
||||
byte[] buff = new byte [1024];
|
||||
for (int len = in.read(buff); len != -1; len = in.read(buff))
|
||||
out.write(buff, 0, len);
|
||||
in.close();
|
||||
out.close();
|
||||
}
|
||||
|
||||
native int initRuntime(String libsDir, String cacheDir);
|
||||
}
|
199
src/mono/msbuild/AndroidAppBuilder/Templates/runtime-android.c
Normal file
199
src/mono/msbuild/AndroidAppBuilder/Templates/runtime-android.c
Normal file
|
@ -0,0 +1,199 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#include <mono/utils/mono-publib.h>
|
||||
#include <mono/utils/mono-logger.h>
|
||||
#include <mono/metadata/assembly.h>
|
||||
#include <mono/metadata/mono-debug.h>
|
||||
#include <mono/metadata/mono-gc.h>
|
||||
#include <mono/metadata/exception.h>
|
||||
#include <mono/jit/jit.h>
|
||||
#include <mono/jit/mono-private-unstable.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <jni.h>
|
||||
#include <android/log.h>
|
||||
#include <sys/system_properties.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static char *bundle_path;
|
||||
|
||||
#define LOG_INFO(fmt, ...) __android_log_print(ANDROID_LOG_DEBUG, "MONO", fmt, ##__VA_ARGS__)
|
||||
#define LOG_ERROR(fmt, ...) __android_log_print(ANDROID_LOG_ERROR, "MONO", fmt, ##__VA_ARGS__)
|
||||
|
||||
static MonoAssembly*
|
||||
load_assembly (const char *name, const char *culture)
|
||||
{
|
||||
char filename [1024];
|
||||
char path [1024];
|
||||
int res;
|
||||
|
||||
LOG_INFO ("assembly_preload_hook: %s %s %s\n", name, culture, bundle_path);
|
||||
|
||||
int len = strlen (name);
|
||||
int has_extension = len > 3 && name [len - 4] == '.' && (!strcmp ("exe", name + (len - 3)) || !strcmp ("dll", name + (len - 3)));
|
||||
|
||||
// add extensions if required.
|
||||
strlcpy (filename, name, sizeof (filename));
|
||||
if (!has_extension) {
|
||||
strlcat (filename, ".dll", sizeof (filename));
|
||||
}
|
||||
|
||||
if (culture && strcmp (culture, ""))
|
||||
res = snprintf (path, sizeof (path) - 1, "%s/%s/%s", bundle_path, culture, filename);
|
||||
else
|
||||
res = snprintf (path, sizeof (path) - 1, "%s/%s", bundle_path, filename);
|
||||
assert (res > 0);
|
||||
|
||||
struct stat buffer;
|
||||
if (stat (path, &buffer) == 0) {
|
||||
MonoAssembly *assembly = mono_assembly_open (path, NULL);
|
||||
assert (assembly);
|
||||
return assembly;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static MonoAssembly*
|
||||
assembly_preload_hook (MonoAssemblyName *aname, char **assemblies_path, void* user_data)
|
||||
{
|
||||
const char *name = mono_assembly_name_get_name (aname);
|
||||
const char *culture = mono_assembly_name_get_culture (aname);
|
||||
return load_assembly (name, culture);
|
||||
}
|
||||
|
||||
char *
|
||||
strdup_printf (const char *msg, ...)
|
||||
{
|
||||
va_list args;
|
||||
char *formatted = NULL;
|
||||
va_start (args, msg);
|
||||
vasprintf (&formatted, msg, args);
|
||||
va_end (args);
|
||||
return formatted;
|
||||
}
|
||||
|
||||
static MonoObject *
|
||||
fetch_exception_property (MonoObject *obj, const char *name, bool is_virtual)
|
||||
{
|
||||
MonoMethod *get = NULL;
|
||||
MonoMethod *get_virt = NULL;
|
||||
MonoObject *exc = NULL;
|
||||
|
||||
get = mono_class_get_method_from_name (mono_get_exception_class (), name, 0);
|
||||
if (get) {
|
||||
if (is_virtual) {
|
||||
get_virt = mono_object_get_virtual_method (obj, get);
|
||||
if (get_virt)
|
||||
get = get_virt;
|
||||
}
|
||||
|
||||
return (MonoObject *) mono_runtime_invoke (get, obj, NULL, &exc);
|
||||
} else {
|
||||
printf ("Could not find the property System.Exception.%s", name);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char *
|
||||
fetch_exception_property_string (MonoObject *obj, const char *name, bool is_virtual)
|
||||
{
|
||||
MonoString *str = (MonoString *) fetch_exception_property (obj, name, is_virtual);
|
||||
return str ? mono_string_to_utf8 (str) : NULL;
|
||||
}
|
||||
|
||||
void
|
||||
unhandled_exception_handler (MonoObject *exc, void *user_data)
|
||||
{
|
||||
MonoClass *type = mono_object_get_class (exc);
|
||||
char *type_name = strdup_printf ("%s.%s", mono_class_get_namespace (type), mono_class_get_name (type));
|
||||
char *trace = fetch_exception_property_string (exc, "get_StackTrace", true);
|
||||
char *message = fetch_exception_property_string (exc, "get_Message", true);
|
||||
|
||||
LOG_ERROR("UnhandledException: %s %s %s", type_name, message, trace);
|
||||
|
||||
free (trace);
|
||||
free (message);
|
||||
free (type_name);
|
||||
exit (1);
|
||||
}
|
||||
|
||||
void
|
||||
log_callback (const char *log_domain, const char *log_level, const char *message, mono_bool fatal, void *user_data)
|
||||
{
|
||||
LOG_INFO ("(%s %s) %s", log_domain, log_level, message);
|
||||
if (fatal) {
|
||||
LOG_ERROR ("Exit code: %d.", 1);
|
||||
exit (1);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
mono_mobile_runtime_init (void)
|
||||
{
|
||||
// uncomment for debug output:
|
||||
//
|
||||
// setenv ("MONO_LOG_LEVEL", "debug", TRUE);
|
||||
// setenv ("MONO_LOG_MASK", "all", TRUE);
|
||||
|
||||
bool wait_for_debugger = false;
|
||||
chdir (bundle_path);
|
||||
|
||||
// TODO: set TRUSTED_PLATFORM_ASSEMBLIES, APP_PATHS and NATIVE_DLL_SEARCH_DIRECTORIES
|
||||
monovm_initialize(0, NULL, NULL);
|
||||
|
||||
mono_debug_init (MONO_DEBUG_FORMAT_MONO);
|
||||
mono_install_assembly_preload_hook (assembly_preload_hook, NULL);
|
||||
mono_install_unhandled_exception_hook (unhandled_exception_handler, NULL);
|
||||
mono_trace_set_log_handler (log_callback, NULL);
|
||||
mono_set_signal_chaining (true);
|
||||
mono_set_crash_chaining (true);
|
||||
|
||||
if (wait_for_debugger) {
|
||||
char* options[] = { "--debugger-agent=transport=dt_socket,server=y,address=0.0.0.0:55555" };
|
||||
mono_jit_parse_options (1, options);
|
||||
}
|
||||
mono_jit_init_version ("dotnet.android", "mobile");
|
||||
|
||||
const char* executable = "%EntryPointLibName%";
|
||||
MonoAssembly *assembly = load_assembly (executable, NULL);
|
||||
assert (assembly);
|
||||
LOG_INFO ("Executable: %s", executable);
|
||||
|
||||
char *managed_argv [1];
|
||||
managed_argv[0] = bundle_path;
|
||||
|
||||
int res = mono_jit_exec (mono_domain_get (), assembly, 1, managed_argv);
|
||||
LOG_INFO ("Exit code: %d.", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
static void
|
||||
strncpy_str (JNIEnv *env, char *buff, jstring str, int nbuff)
|
||||
{
|
||||
jboolean isCopy = 0;
|
||||
const char *copy_buff = (*env)->GetStringUTFChars (env, str, &isCopy);
|
||||
strncpy (buff, copy_buff, nbuff);
|
||||
if (isCopy)
|
||||
(*env)->ReleaseStringUTFChars (env, str, copy_buff);
|
||||
}
|
||||
|
||||
int
|
||||
Java_net_dot_MonoRunner_initRuntime (JNIEnv* env, jobject thiz, jstring j_files_dir, jstring j_cache_dir)
|
||||
{
|
||||
char file_dir[2048];
|
||||
char cache_dir[2048];
|
||||
strncpy_str (env, file_dir, j_files_dir, sizeof(file_dir));
|
||||
strncpy_str (env, cache_dir, j_cache_dir, sizeof(cache_dir));
|
||||
|
||||
bundle_path = file_dir;
|
||||
setenv ("HOME", bundle_path, true);
|
||||
setenv ("TMPDIR", cache_dir, true);
|
||||
return mono_mobile_runtime_init ();
|
||||
}
|
114
src/mono/msbuild/AndroidAppBuilder/Utils.cs
Normal file
114
src/mono/msbuild/AndroidAppBuilder/Utils.cs
Normal file
|
@ -0,0 +1,114 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Microsoft.Build.Framework;
|
||||
using Microsoft.Build.Utilities;
|
||||
|
||||
internal class Utils
|
||||
{
|
||||
public static string GetEmbeddedResource(string file)
|
||||
{
|
||||
using Stream stream = typeof(Utils).Assembly
|
||||
.GetManifestResourceStream($"{typeof(Utils).Assembly.GetName().Name}.Templates.{file}")!;
|
||||
using var reader = new StreamReader(stream);
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
|
||||
public static string RunProcess(
|
||||
string path,
|
||||
string args = "",
|
||||
IDictionary<string, string>? envVars = null,
|
||||
string? workingDir = null,
|
||||
bool ignoreErrors = false,
|
||||
bool silent = false)
|
||||
{
|
||||
LogInfo($"Running: {path} {args}");
|
||||
var outputBuilder = new StringBuilder();
|
||||
var errorBuilder = new StringBuilder();
|
||||
var processStartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = path,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardError = true,
|
||||
RedirectStandardOutput = true,
|
||||
Arguments = args,
|
||||
};
|
||||
|
||||
if (workingDir != null)
|
||||
processStartInfo.WorkingDirectory = workingDir;
|
||||
|
||||
if (envVars != null)
|
||||
{
|
||||
foreach (KeyValuePair<string, string> envVar in envVars)
|
||||
processStartInfo.EnvironmentVariables[envVar.Key] = envVar.Value;
|
||||
}
|
||||
|
||||
Process? process = Process.Start(processStartInfo);
|
||||
if (process == null)
|
||||
throw new ArgumentException("Process.Start({path} {args}) returned null process");
|
||||
|
||||
process.ErrorDataReceived += (sender, e) =>
|
||||
{
|
||||
if (!silent)
|
||||
{
|
||||
LogError(e.Data);
|
||||
outputBuilder.AppendLine(e.Data);
|
||||
errorBuilder.AppendLine(e.Data);
|
||||
}
|
||||
};
|
||||
process.OutputDataReceived += (sender, e) =>
|
||||
{
|
||||
if (!silent)
|
||||
{
|
||||
LogInfo(e.Data);
|
||||
outputBuilder.AppendLine(e.Data);
|
||||
}
|
||||
};
|
||||
process.BeginOutputReadLine();
|
||||
process.BeginErrorReadLine();
|
||||
process.WaitForExit();
|
||||
|
||||
if (!ignoreErrors && process.ExitCode != 0)
|
||||
throw new Exception("Error: " + errorBuilder);
|
||||
|
||||
return outputBuilder.ToString().Trim('\r','\n');
|
||||
}
|
||||
|
||||
public static void DirectoryCopy(string sourceDir, string destDir, Func<string, bool> predicate)
|
||||
{
|
||||
string[] files = Directory.GetFiles(sourceDir, "*", SearchOption.AllDirectories);
|
||||
foreach (string file in files)
|
||||
{
|
||||
if (!predicate(file))
|
||||
continue;
|
||||
|
||||
string relativePath = Path.GetRelativePath(sourceDir, file);
|
||||
string? relativeDir = Path.GetDirectoryName(relativePath);
|
||||
if (!string.IsNullOrEmpty(relativeDir))
|
||||
Directory.CreateDirectory(Path.Combine(destDir, relativeDir));
|
||||
|
||||
File.Copy(file, Path.Combine(destDir, relativePath), true);
|
||||
}
|
||||
}
|
||||
|
||||
public static TaskLoggingHelper? Logger { get; set; }
|
||||
|
||||
public static void LogInfo(string? msg)
|
||||
{
|
||||
if (msg != null)
|
||||
Logger?.LogMessage(MessageImportance.High, msg);
|
||||
}
|
||||
|
||||
public static void LogError(string? msg)
|
||||
{
|
||||
if (msg != null)
|
||||
Logger?.LogError(msg);
|
||||
}
|
||||
}
|
89
src/mono/msbuild/AndroidTestRunner/AndroidTestRunner.cs
Normal file
89
src/mono/msbuild/AndroidTestRunner/AndroidTestRunner.cs
Normal file
|
@ -0,0 +1,89 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.DotNet.XHarness.Tests.Runners;
|
||||
using Microsoft.DotNet.XHarness.Tests.Runners.Core;
|
||||
|
||||
public class SimpleAndroidTestRunner : AndroidApplicationEntryPoint, IDevice
|
||||
{
|
||||
private static List<string> s_testLibs = new List<string>();
|
||||
private static string? s_MainTestName;
|
||||
|
||||
public static async Task<int> Main(string[] args)
|
||||
{
|
||||
s_testLibs = Directory.GetFiles(Environment.CurrentDirectory, "*.Tests.dll").ToList();
|
||||
if (s_testLibs.Count < 1)
|
||||
{
|
||||
Console.WriteLine($"Test libs were not found (*.Tests.dll was not found in {Environment.CurrentDirectory})");
|
||||
return -1;
|
||||
}
|
||||
s_MainTestName = Path.GetFileNameWithoutExtension(s_testLibs[0]);
|
||||
var simpleTestRunner = new SimpleAndroidTestRunner(true);
|
||||
await simpleTestRunner.RunAsync();
|
||||
Console.WriteLine("----- Done -----");
|
||||
return 0;
|
||||
}
|
||||
|
||||
public SimpleAndroidTestRunner(bool verbose)
|
||||
{
|
||||
if (verbose)
|
||||
{
|
||||
MinimumLogLevel = MinimumLogLevel.Verbose;
|
||||
_maxParallelThreads = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
MinimumLogLevel = MinimumLogLevel.Info;
|
||||
_maxParallelThreads = Environment.ProcessorCount;
|
||||
}
|
||||
}
|
||||
|
||||
protected override IEnumerable<TestAssemblyInfo> GetTestAssemblies()
|
||||
{
|
||||
foreach (string file in s_testLibs)
|
||||
{
|
||||
yield return new TestAssemblyInfo(Assembly.LoadFrom(file), file);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void TerminateWithSuccess()
|
||||
{
|
||||
Console.WriteLine("[TerminateWithSuccess]");
|
||||
}
|
||||
|
||||
private int? _maxParallelThreads;
|
||||
|
||||
protected override int? MaxParallelThreads => _maxParallelThreads;
|
||||
|
||||
protected override IDevice Device => this;
|
||||
|
||||
protected override TestRunnerType TestRunner => TestRunnerType.Xunit;
|
||||
|
||||
protected override string? IgnoreFilesDirectory => null;
|
||||
|
||||
public string BundleIdentifier => "net.dot." + s_MainTestName;
|
||||
|
||||
public string? UniqueIdentifier { get; }
|
||||
|
||||
public string? Name { get; }
|
||||
|
||||
public string? Model { get; }
|
||||
|
||||
public string? SystemName { get; }
|
||||
|
||||
public string? SystemVersion { get; }
|
||||
|
||||
public string? Locale { get; }
|
||||
|
||||
public override TextWriter? Logger => null;
|
||||
|
||||
public override string TestsResultsFinalPath =>
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "testResults.xml");
|
||||
}
|
11
src/mono/msbuild/AndroidTestRunner/AndroidTestRunner.csproj
Normal file
11
src/mono/msbuild/AndroidTestRunner/AndroidTestRunner.csproj
Normal file
|
@ -0,0 +1,11 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<OutputPath>bin</OutputPath>
|
||||
<TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.DotNet.XHarness.Tests.Runners" Version="$(MicrosoftDotNetXHarnessTestsRunnersVersion)" />
|
||||
</ItemGroup>
|
||||
</Project>
|
21
src/mono/netcore/sample/Android/Makefile
Normal file
21
src/mono/netcore/sample/Android/Makefile
Normal file
|
@ -0,0 +1,21 @@
|
|||
MONO_CONFIG=Debug
|
||||
MONO_ARCH=arm64
|
||||
DOTNET := ../../../../.././dotnet.sh
|
||||
|
||||
#export ANDROID_NDK_ROOT=/path/to/android/ndk
|
||||
#export ANDROID_SDK_ROOT=/path/to/android/sdk
|
||||
|
||||
all: runtimepack bundle
|
||||
|
||||
bundle: clean
|
||||
$(DOTNET) build -c $(MONO_CONFIG) Program.csproj
|
||||
$(DOTNET) msbuild /t:BuildAppBundle /p:Configuration=$(MONO_CONFIG) /p:TargetArchitecture=$(MONO_ARCH)
|
||||
|
||||
deploy-launch: bundle
|
||||
$(DOTNET) msbuild /t:ReinstallAndLaunch
|
||||
|
||||
runtimepack:
|
||||
../../../../.././build.sh -c $(MONO_CONFIG) -os Android -arch $(MONO_ARCH) -subset Mono+Libs /p:DisableCrossgen=true
|
||||
|
||||
clean:
|
||||
rm -rf bin
|
14
src/mono/netcore/sample/Android/Program.cs
Normal file
14
src/mono/netcore/sample/Android/Program.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
public static class Program
|
||||
{
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
Console.WriteLine("Hello, Android!"); // logcat
|
||||
return 42;
|
||||
}
|
||||
}
|
62
src/mono/netcore/sample/Android/Program.csproj
Normal file
62
src/mono/netcore/sample/Android/Program.csproj
Normal file
|
@ -0,0 +1,62 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<OutputPath>bin</OutputPath>
|
||||
<TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
|
||||
<TargetArchitecture Condition="'$(TargetArchitecture)'==''">x64</TargetArchitecture>
|
||||
<RuntimePackDir>$(ArtifactsDir)bin\lib-runtime-packs\runtimes\android-$(TargetArchitecture)</RuntimePackDir>
|
||||
<BundleDir>$(MSBuildThisFileDirectory)\bin\bundle</BundleDir>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="RebuildAndroidAppBuilder">
|
||||
<MSBuild Projects="$(RepoRoot)src\mono\msbuild\AndroidAppBuilder\AndroidAppBuilder.csproj"
|
||||
Properties="Configuration=$(Configuration)" Targets="Restore;Build" />
|
||||
</Target>
|
||||
|
||||
<UsingTask TaskName="AndroidAppBuilderTask"
|
||||
AssemblyFile="$(ArtifactsObjDir)mono\AndroidAppBuilder\$(TargetArchitecture)\$(Configuration)\AndroidAppBuilder.dll" />
|
||||
|
||||
<Target Name="BuildAppBundle" DependsOnTargets="RebuildAndroidAppBuilder">
|
||||
<PropertyGroup>
|
||||
<AndroidAbi Condition="'$(TargetArchitecture)'=='arm64'">arm64-v8a</AndroidAbi>
|
||||
<AndroidAbi Condition="'$(TargetArchitecture)'=='arm'">armeabi</AndroidAbi>
|
||||
<AndroidAbi Condition="'$(TargetArchitecture)'=='x64'">x86_64</AndroidAbi>
|
||||
<AndroidAbi Condition="'$(AndroidAbi)'==''">$(TargetArchitecture)</AndroidAbi>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<AppBinaries Include="bin\*.*"/>
|
||||
<BclBinaries Include="$(RuntimePackDir)\lib\$(NetCoreAppCurrent)\*.*"
|
||||
Exclude="$(RuntimePackDir)\lib\$(NetCoreAppCurrent)\System.Runtime.WindowsRuntime.dll" />
|
||||
<BclBinaries Include="$(RuntimePackDir)\native\*.*" />
|
||||
</ItemGroup>
|
||||
<Error Condition="'$(AndroidAbi)'==''" Text="Unknown $(TargetArchitecture)" />
|
||||
<Error Condition="!Exists('$(RuntimePackDir)')" Text="RuntimePackDir=$(RuntimePackDir) doesn't exist" />
|
||||
<RemoveDir Directories="$(BundleDir)" />
|
||||
<Copy SourceFiles="@(AppBinaries)" DestinationFolder="$(BundleDir)" SkipUnchangedFiles="true"/>
|
||||
<Copy SourceFiles="@(BclBinaries)" DestinationFolder="$(BundleDir)\%(RecursiveDir)" SkipUnchangedFiles="true"/>
|
||||
<AndroidAppBuilderTask
|
||||
Abi="$(AndroidAbi)"
|
||||
ProjectName="HelloAndroid"
|
||||
MonoRuntimeHeaders="$(RuntimePackDir)\native\include\mono-2.0"
|
||||
MainLibraryFileName="Program.dll"
|
||||
SourceDir="$(BundleDir)"
|
||||
OutputDir="$(BundleDir)\apk">
|
||||
<Output TaskParameter="ApkBundlePath" PropertyName="ApkBundlePath" />
|
||||
<Output TaskParameter="ApkPackageId" PropertyName="ApkPackageId" />
|
||||
</AndroidAppBuilderTask>
|
||||
<Message Importance="High" Text="Apk: $(ApkBundlePath)"/>
|
||||
<Message Importance="High" Text="PackageId: $(ApkPackageId)"/>
|
||||
</Target>
|
||||
|
||||
<!-- Deploy and launch on an active emulator or device -->
|
||||
<Target Name="ReinstallAndLaunch">
|
||||
<PropertyGroup>
|
||||
<AdbTool>$(ANDROID_SDK_ROOT)\platform-tools\adb</AdbTool>
|
||||
</PropertyGroup>
|
||||
<Message Importance="High" Text="Uninstalling app if it exists (throws an error if it doesn't but it can be ignored):"/>
|
||||
<Exec Command="$(AdbTool) uninstall net.dot.HelloAndroid" ContinueOnError="WarnAndContinue" />
|
||||
<Exec Command="$(AdbTool) install bin/bundle/apk/bin/HelloAndroid.apk" />
|
||||
<Exec Command="$(AdbTool) shell am instrument -w net.dot.HelloAndroid/net.dot.MonoRunner" />
|
||||
<!--Exec Command="$(AdbTool) logcat" /-->
|
||||
</Target>
|
||||
</Project>
|
Loading…
Add table
Add a link
Reference in a new issue