Fixes: https://github.com/xamarin/xamarin-android/issues/4191

Context: https://github.com/xamarin/xamarin-android/projects/14

Add `tools/apkdiff`, a tool for comparing `.apk` files.

It has three modes of operation:

 1. Compare two `.apk` files:

        apkdiff App1.apk App2.apk

 2. Create a `.apkdesc` file from a `.apk`, which is a JSON file
    listing all the entries in the `.apk` and their sizes:

        apkdiff -s App1.apk App1.apkdesc

 3. Compare a `.apk` file to an `.apkdesc` file:

        apkdiff App1.apk App1.apkdesc

The `.apkdesc` JSON files will eventually replace the `.csv` files
within `tests/apk-sizes-reference` (e8b9ee21), and look like:

	{
	  "Comment": "HEAD/master: cdc04224edcb876ae6607693ea4011fee8c76893",
	  "PackageSize": 75817581,
	  "Entries": {
	    "AndroidManifest.xml": {
	      "Size": 5428
	    },
	    "res/drawable/android_button.xml": {
	      "Size": 588
	    },
	  ...
	}

Example "diff" output:

	$ mono apkdiff.exe xa-d16-4/bin/TestRelease/Xamarin.Forms_Performance_Integration.apkdesc xa-d16-5/bin/TestRelease/Xamarin.Forms_Performance_Integration.apk
	 Size difference in bytes ([*1] apk1 only, [*2] apk2 only):
	   +       49184 lib/armeabi-v7a/libmonosgen-2.0.so
	   +       13824 assemblies/Mono.Android.dll
	   +       10824 lib/x86/libmonodroid.so
	   +        5604 lib/armeabi-v7a/libmonodroid.so
	   +        1864 lib/armeabi-v7a/libxamarin-app.so
	   +        1864 lib/x86/libxamarin-app.so
	   +         168 classes.dex
	   -        3584 assemblies/System.dll
	   -       10240 assemblies/mscorlib.dll
	   -       71680 assemblies/Mono.Security.dll
	   -       77792 lib/x86/libmonosgen-2.0.so
	 Summary:
	   -       46984 Package size difference
This commit is contained in:
Radek Doulik 2020-02-07 16:39:59 +01:00 коммит произвёл GitHub
Родитель 1273970bcc
Коммит 2e28f2eaee
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
13 изменённых файлов: 422 добавлений и 8 удалений

Просмотреть файл

@ -129,6 +129,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "jnienv-gen", "external\Java
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "check-boot-times", "build-tools\check-boot-times\check-boot-times.csproj", "{D28957BF-5E66-4D60-B528-22820C60AC82}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "apkdiff", "tools\apkdiff\apkdiff.csproj", "{4E0D89AC-1C8A-45A8-94F0-A54D1B68BE9C}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Xamarin.Android.NamingCustomAttributes\Xamarin.Android.NamingCustomAttributes.projitems*{3f1f2f50-af1a-4a5a-bedb-193372f068d7}*SharedItemsImports = 4
@ -368,6 +370,10 @@ Global
{D28957BF-5E66-4D60-B528-22820C60AC82}.Debug|AnyCPU.Build.0 = Debug|Any CPU
{D28957BF-5E66-4D60-B528-22820C60AC82}.Release|AnyCPU.ActiveCfg = Release|Any CPU
{D28957BF-5E66-4D60-B528-22820C60AC82}.Release|AnyCPU.Build.0 = Release|Any CPU
{4E0D89AC-1C8A-45A8-94F0-A54D1B68BE9C}.Debug|AnyCPU.ActiveCfg = Debug|anycpu
{4E0D89AC-1C8A-45A8-94F0-A54D1B68BE9C}.Debug|AnyCPU.Build.0 = Debug|anycpu
{4E0D89AC-1C8A-45A8-94F0-A54D1B68BE9C}.Release|AnyCPU.ActiveCfg = Release|anycpu
{4E0D89AC-1C8A-45A8-94F0-A54D1B68BE9C}.Release|AnyCPU.Build.0 = Release|anycpu
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -431,6 +437,7 @@ Global
{DE40756E-57F6-4AF2-B155-55E3A88CCED8} = {05C3B1D6-A4CE-4534-A9E4-E9117591ADF7}
{6410DA0F-5E14-4FC0-9AEE-F4C542C96C7A} = {05C3B1D6-A4CE-4534-A9E4-E9117591ADF7}
{D28957BF-5E66-4D60-B528-22820C60AC82} = {E351F97D-EA4F-4E7F-AAA0-8EBB1F2A4A62}
{4E0D89AC-1C8A-45A8-94F0-A54D1B68BE9C} = {864062D3-A415-4A6F-9324-5820237BA058}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {53A1F287-EFB2-4D97-A4BB-4A5E145613F6}

Просмотреть файл

@ -30,6 +30,7 @@
</ItemGroup>
<ItemGroup>
<_TestResultFiles Include="$(XamarinAndroidSourcePath)TestResult-*.xml" />
<_TestResultFiles Include="$(XamarinAndroidSourcePath)bin\Test$(Configuration)\*.apkdesc" />
<_TestResultFiles Include="$(XamarinAndroidSourcePath)bin\Test$(Configuration)\TestResult-*.xml" />
<_TestResultFiles Include="$(XamarinAndroidSourcePath)bin\Test$(Configuration)\compatibility\*" />
<_TestResultFiles Include="$(XamarinAndroidSourcePath)bin\Test$(Configuration)\logcat*" />

Просмотреть файл

@ -102,6 +102,8 @@
</ItemGroup>
<ItemGroup>
<_MSBuildFiles Include="$(MSBuildSrcDir)\android-support-multidex.jar" />
<_MSBuildFiles Include="$(MSBuildSrcDir)\apkdiff.exe" />
<_MSBuildFiles Include="$(MSBuildSrcDir)\apkdiff.pdb" />
<_MSBuildFiles Include="$(MSBuildSrcDir)\aprofutil.exe" />
<_MSBuildFiles Include="$(MSBuildSrcDir)\aprofutil.pdb" />
<_MSBuildFiles Include="$(MSBuildSrcDir)\cil-strip.exe" />
@ -157,6 +159,8 @@
<_MSBuildFiles Include="$(MSBuildSrcDir)\Mono.Posix.NETStandard.dll" />
<_MSBuildFiles Include="$(MSBuildSrcDir)\Mono.Profiler.Log.dll" />
<_MSBuildFiles Include="$(MSBuildSrcDir)\Mono.Profiler.Log.pdb" />
<_MSBuildFiles Include="$(MSBuildSrcDir)\Newtonsoft.Json.dll" />
<_MSBuildFiles Include="$(MSBuildSrcDir)\Newtonsoft.Json-LICENSE.md" />
<_MSBuildFiles Include="$(MSBuildSrcDir)\logcat-parse.exe" />
<_MSBuildFiles Include="$(MSBuildSrcDir)\logcat-parse.pdb" />
<_MSBuildFiles Include="$(MSBuildSrcDir)\mdoc.exe" />
@ -286,6 +290,7 @@
<_MSBuildFilesUnixSwab Include="$(MSBuildSrcDir)\$(HostOS)\ndk\x86_64-linux-android-as" />
<_MSBuildFilesUnixSwab Include="$(MSBuildSrcDir)\$(HostOS)\ndk\x86_64-linux-android-ld" />
<_MSBuildFilesUnixSwab Include="$(MSBuildSrcDir)\$(HostOS)\ndk\x86_64-linux-android-strip" />
<_MSBuildFilesUnix Include="$(MSBuildSrcDir)\$(HostOS)\apkdiff" />
<_MSBuildFilesUnix Include="$(MSBuildSrcDir)\$(HostOS)\illinkanalyzer" />
<_MSBuildFilesUnix Include="$(MSBuildSrcDir)\$(HostOS)\jit-times" />
<_MSBuildFilesUnix Include="$(MSBuildSrcDir)\$(HostOS)\aprofutil" />

Просмотреть файл

@ -347,6 +347,15 @@
LabelSuffix="-$(Configuration)$(TestsFlavor)"
ContinueOnError="ErrorAndContinue"
/>
<PropertyGroup>
<ApkDiffPath>$(XAInstallPrefix)xbuild\Xamarin\Android\$(HostOS)\apkdiff</ApkDiffPath>
<ApkDiffPath Condition=" !Exists('$(ApkDiffPath)') ">$(MonoAndroidBinDirectory)\apkdiff</ApkDiffPath>
</PropertyGroup>
<Exec
Condition=" Exists('$(ApkDiffPath)') "
Command="&quot;$(ApkDiffPath)&quot; -v -c &quot;HEAD/`git branch --show-current`: `git rev-parse HEAD`&quot; -s &quot;%(_AllArchives.Identity)&quot;;mv &quot;%(_AllArchives.RelativeDir)\%(_AllArchives.Filename).apkdesc&quot; &quot;%(_AllArchives.RelativeDir)\%(_AllArchives.Filename)-$(Configuration)$(TestsFlavor).apkdesc&quot;"
ContinueOnError="ErrorAndContinue"
/>
</Target>
<Target Name="CheckBootTimes"
DependsOnTargets="AcquireAndroidTarget;ReleaseAndroidTarget">

Просмотреть файл

@ -10,19 +10,28 @@ namespace Xamarin.Android.Prepare
: base ("Preparing local components")
{}
async Task<bool> Restore (MSBuildRunner msbuild, string csprojPath, string logTag, string binLogName)
{
return await msbuild.Run (
projectPath: csprojPath,
logTag: logTag,
arguments: new List<string> {
"/t:Restore"
},
binlogName: binLogName
);
}
protected override async Task<bool> Execute(Context context)
{
var msbuild = new MSBuildRunner (context);
string xfTestPath = Path.Combine (BuildPaths.XamarinAndroidSourceRoot, "tests", "Xamarin.Forms-Performance-Integration", "Xamarin.Forms.Performance.Integration.csproj");
return await msbuild.Run (
projectPath: xfTestPath,
logTag: "xfperf",
arguments: new List <string> {
"/t:Restore"
},
binlogName: "prepare-restore"
);
if (!await Restore (msbuild, xfTestPath, "xfperf", "prepare-restore"))
return false;
var apkDiffPath = Path.Combine (BuildPaths.XamarinAndroidSourceRoot, "tools", "apkdiff", "apkdiff.csproj");
return await Restore (msbuild, apkDiffPath, "apkdiff", "prepare-restore-apkdiff");
}
}
}

Просмотреть файл

@ -274,6 +274,7 @@
<_MonoScript Include="illinkanalyzer" />
<_MonoScript Include="jit-times" />
<_MonoScript Include="aprofutil" />
<_MonoScript Include="apkdiff" />
<_MonoScriptSource Include="@(_MonoScript->'$(_MonoScriptSourceDirectory)\%(Identity)')" />
<_MonoScriptSource Include="mono.config" />
<_MonoScriptDestination Include="@(_MonoScript->'$(_MonoScriptDestinationDirectory)\%(Identity)')" />

Просмотреть файл

@ -0,0 +1,151 @@
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Runtime.Serialization;
using Xamarin.Tools.Zip;
namespace apkdiff {
struct FileInfo {
public long Size;
}
[DataContract (Namespace = "apk")]
public class ApkDescription {
[DataMember]
string Comment;
[DataMember]
long PackageSize;
string PackagePath;
[DataMember]
readonly Dictionary<string, FileInfo> Entries = new Dictionary<string, FileInfo> ();
public static ApkDescription Load (string path)
{
if (!File.Exists (path)) {
Program.Error ($"File '{path}' does not exist.");
Environment.Exit (2);
}
var extension = Path.GetExtension (path);
switch (extension.ToLower ()) {
case ".apk":
var nd = new ApkDescription ();
nd.LoadApk (path);
return nd;
case ".apkdesc":
return LoadDescription (path);
default:
Program.Error ($"Unknown file extension '{extension}'");
Environment.Exit (3);
return null;
}
}
void LoadApk (string path)
{
var zip = ZipArchive.Open (path, FileMode.Open);
if (Program.Verbose)
Program.ColorWriteLine ($"Loading apk '{path}'", ConsoleColor.Yellow);
PackageSize = new System.IO.FileInfo (path).Length;
PackagePath = path;
foreach (var entry in zip) {
var name = entry.FullName;
if (Entries.ContainsKey (name)) {
Program.Warning ("Duplicate APK file entry: {name}");
continue;
}
Entries [name] = new FileInfo { Size = (long)entry.Size };
if (Program.Verbose)
Program.ColorWriteLine ($" {entry.Size,12} {name}", ConsoleColor.Gray);
}
if (Program.SaveDescriptions) {
var descPath = Path.ChangeExtension (path, ".apkdesc");
Program.ColorWriteLine ($"Saving apk description to '{descPath}'", ConsoleColor.Yellow);
SaveDescription (descPath);
}
}
static ApkDescription LoadDescription (string path)
{
if (Program.Verbose)
Program.ColorWriteLine ($"Loading description '{path}'", ConsoleColor.Yellow);
using (var reader = File.OpenText (path)) {
return new Newtonsoft.Json.JsonSerializer ().Deserialize (reader, typeof (ApkDescription)) as ApkDescription;
}
}
void SaveDescription (string path)
{
Comment = Program.Comment;
using (var writer = File.CreateText (path)) {
new Newtonsoft.Json.JsonSerializer () { Formatting = Newtonsoft.Json.Formatting.Indented }.Serialize (writer, this);
}
}
void PrintDifference (string key, long diff, string comment = null)
{
var color = diff > 0 ? ConsoleColor.Red : ConsoleColor.Green;
Program.ColorWrite ($" {diff:+;-;+}{Math.Abs (diff),12}", color);
Program.ColorWrite ($" {key}", ConsoleColor.Gray);
Program.ColorWriteLine (comment, color);
}
public void Compare (ApkDescription other)
{
var keys = Entries.Keys.Union (other.Entries.Keys);
var differences = new Dictionary<string, long> ();
var singles = new HashSet<string> ();
Program.ColorWriteLine ("Size difference in bytes ([*1] apk1 only, [*2] apk2 only):", ConsoleColor.Yellow);
foreach (var key in Entries.Keys) {
if (other.Entries.ContainsKey (key)) {
differences [key] = other.Entries [key].Size - Entries [key].Size;
} else {
differences [key] = -Entries [key].Size;
singles.Add (key);
}
}
foreach (var key in other.Entries.Keys) {
if (Entries.ContainsKey (key))
continue;
differences [key] = other.Entries [key].Size;
singles.Add (key);
}
foreach (var diff in differences.OrderByDescending (v => v.Value)) {
if (diff.Value == 0)
continue;
PrintDifference (diff.Key, diff.Value, singles.Contains (diff.Key) ? $" *{(diff.Value > 0 ? 2 : 1)}" : null);
}
Program.ColorWriteLine ("Summary:", ConsoleColor.Green);
if (Program.Verbose)
Program.ColorWriteLine ($" apk1: {PackageSize,12} {PackagePath}\n apk2: {other.PackageSize,12} {other.PackagePath}", ConsoleColor.Gray);
PrintDifference ("Package size difference", other.PackageSize - PackageSize);
}
}
}

89
tools/apkdiff/Program.cs Normal file
Просмотреть файл

@ -0,0 +1,89 @@
using System;
using System.IO;
using Mono.Options;
using static System.Console;
namespace apkdiff {
class Program {
static readonly string Name = "apkdiff";
public static string Comment;
public static bool SaveDescriptions;
public static bool Verbose;
public static void Main (string [] args)
{
var (path1, path2) = ProcessArguments (args);
var desc1 = ApkDescription.Load (path1);
if (path2 != null) {
var desc2 = ApkDescription.Load (path2);
desc1.Compare (desc2);
}
}
static (string, string) ProcessArguments (string [] args)
{
var help = false;
var options = new OptionSet {
$"Usage: {Name}.exe OPTIONS* <package1.apk[desc]> [<package2.apk[desc]>]",
"",
"Compares APK packages content or APK package with content description",
"",
"Copyright 2020 Microsoft Corporation",
"",
"Options:",
{ "c|comment=",
"Comment to be saved inside .apkdesc file",
v => Comment = v },
{ "h|help|?",
"Show this message and exit",
v => help = v != null },
{ "s|save-descriptions",
"Save .apkdesc files next to the apk package(s)",
v => SaveDescriptions = true },
{ "v|verbose",
"Output information about progress during the run of the tool",
v => Verbose = true },
};
var remaining = options.Parse (args);
if (help || args.Length < 1) {
options.WriteOptionDescriptions (Out);
Environment.Exit (0);
}
if (remaining.Count != 2 && (remaining.Count != 1 || !SaveDescriptions)) {
Error ("Please specify 2 APK packages to compare or 1 and use -s option.");
Environment.Exit (1);
}
return (remaining [0], remaining.Count > 1 ? remaining [1] : null);
}
static void ColorMessage (string message, ConsoleColor color, TextWriter writer, bool writeLine = true)
{
ForegroundColor = color;
if (writeLine)
writer.WriteLine (message);
else
writer.Write (message);
ResetColor ();
}
public static void ColorWriteLine (string message, ConsoleColor color) => ColorMessage (message, color, Out);
public static void ColorWrite (string message, ConsoleColor color) => ColorMessage (message, color, Out, false);
public static void Error (string message) => ColorMessage ($"Error: {Name}: {message}", ConsoleColor.Red, Console.Error);
public static void Warning (string message) => ColorMessage ($"Warning: {Name}: {message}", ConsoleColor.Yellow, Console.Error);
}
}

Просмотреть файл

@ -0,0 +1,26 @@
using System.Reflection;
using System.Runtime.CompilerServices;
// Information about this assembly is defined by the following attributes.
// Change them to the values specific to your project.
[assembly: AssemblyTitle ("apkdiff")]
[assembly: AssemblyDescription ("")]
[assembly: AssemblyConfiguration ("")]
[assembly: AssemblyCompany ("Microsoft Corporation")]
[assembly: AssemblyProduct ("")]
[assembly: AssemblyCopyright ("2020 Microsoft Corporation")]
[assembly: AssemblyTrademark ("")]
[assembly: AssemblyCulture ("")]
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
[assembly: AssemblyVersion ("0.0.1")]
// The following attributes are used to specify the signing key for the assembly,
// if desired. See the Mono documentation for more information about signing.
//[assembly: AssemblyDelaySign(false)]
//[assembly: AssemblyKeyFile("")]

39
tools/apkdiff/README.md Normal file
Просмотреть файл

@ -0,0 +1,39 @@
**apkdiff** is a tool to compare Android packages
```
Usage: apkdiff.exe OPTIONS* <package1.apk> <package2.apk>
Compares APK packages content or APK package with content description
Copyright 2020 Microsoft Corporation
Options:
-c, --comment=VALUE Comment to be saved inside .apkdesc file
-h, --help, -? Show this message and exit
-s, --save-descriptions Save .apkdesc files next to the apk package(s)
-v, --verbose Output information about progress during the run
of the tool
```
It can be use to compare Android packages (apk's) and/or apk
descriptions files (apkdesc)
### Example usage
```
mono apkdiff.exe xa-d16-4/bin/TestRelease/Xamarin.Forms_Performance_Integration.apkdesc xa-d16-5/bin/TestRelease/Xamarin.Forms_Performance_Integration.apk
Size difference in bytes ([*1] apk1 only, [*2] apk2 only):
+ 49184 lib/armeabi-v7a/libmonosgen-2.0.so
+ 13824 assemblies/Mono.Android.dll
+ 10824 lib/x86/libmonodroid.so
+ 5604 lib/armeabi-v7a/libmonodroid.so
+ 1864 lib/armeabi-v7a/libxamarin-app.so
+ 1864 lib/x86/libxamarin-app.so
+ 168 classes.dex
- 3584 assemblies/System.dll
- 10240 assemblies/mscorlib.dll
- 71680 assemblies/Mono.Security.dll
- 77792 lib/x86/libmonosgen-2.0.so
Summary:
- 46984 Package size difference
```

Просмотреть файл

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{4E0D89AC-1C8A-45A8-94F0-A54D1B68BE9C}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>apkdiff</RootNamespace>
<AssemblyName>apkdiff</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
</PropertyGroup>
<Import Project="..\..\Configuration.props" />
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>$(XAInstallPrefix)xbuild\Xamarin\Android\</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ExternalConsole>true</ExternalConsole>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<Optimize>true</Optimize>
<OutputPath>$(XAInstallPrefix)xbuild\Xamarin\Android\</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ExternalConsole>true</ExternalConsole>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="Mono.Posix.NETStandard">
<HintPath>packages\Mono.Posix.NETStandard.1.0.0\lib\net40\Mono.Posix.NETStandard.dll</HintPath>
</Reference>
<Reference Include="Mono.Posix" />
<Reference Include="libZipSharp">
<HintPath>packages\Xamarin.LibZipSharp.1.0.8\lib\net45\libZipSharp.dll</HintPath>
</Reference>
<Reference Include="Mono.Options">
<HintPath>packages\Mono.Options.6.6.0.161\lib\net40\Mono.Options.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json">
<HintPath>packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Serialization" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ApkDescription.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Mono.Options" Version="6.6.0.161" />
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
<PackageReference Include="Xamarin.LibZipSharp" Version="1.0.8" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="packages\Xamarin.LibZipSharp.1.0.8\build\Xamarin.LibZipSharp.targets" Condition="Exists('packages\Xamarin.LibZipSharp.1.0.8\build\Xamarin.LibZipSharp.targets')" />
<Import Project="apkdiff.targets" />
</Project>

Просмотреть файл

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="CopyNewtonsoftJsonLicense"
AfterTargets="Build">
<Copy
SourceFiles="..\..\packages\newtonsoft.json\12.0.3\LICENSE.md"
DestinationFiles="$(OutputPath)\Newtonsoft.Json-LICENSE.md"
/>
</Target>
</Project>

6
tools/scripts/apkdiff Executable file
Просмотреть файл

@ -0,0 +1,6 @@
#!/bin/sh
BINDIR=`dirname "$0"`
MANDROID_DIR="$BINDIR/.."
unset MONO_PATH
exec mono $MONO_OPTIONS "$MANDROID_DIR/apkdiff.exe" "$@"