Skip to content

Commit

Permalink
[Xamarin.Android.Build.Tasks] Use Google Manifest Merger. (#3918)
Browse files Browse the repository at this point in the history
Use the Google Manifest Merger tooling to merge library
`AndroidManifest.xml` files into the App's `AndroidManifest.xml`.
This should allow developers to override and remove some unwanted
permissions which are currently automatically imported from some of
the support libraries.

Documentation on how to use the new manifest merger tooling is at:

	 https://developer.android.com/studio/build/manifest-merge

Because of the limitations in Command line argument lengths under
Windows we have to build our own entry point for the manifest merger
tool.  Our entry point takes a single "response" file as an input,
with each argument needs to be on a separate line for example:

	--main
	AndroidManifest.xml
	--libs
	obj\Debug\lp\0\ji\AndroidManifest.xml:obj\Debug\lp\2\ji\AndroidManifest.xml
	--out
	android\AndroidManifest.xml

We have to put each argument on its own line because of the way the
built in Manifest Merger Argument parser works.
  • Loading branch information
dellis1972 authored and jonpryor committed Dec 6, 2019
1 parent aa05045 commit 2c6f5cd
Show file tree
Hide file tree
Showing 20 changed files with 350 additions and 80 deletions.
1 change: 1 addition & 0 deletions Configuration.props
Expand Up @@ -103,6 +103,7 @@
<XAPlatformToolsVersion>29.0.1</XAPlatformToolsVersion>
<XAIncludeProprietaryBits Condition="'$(XAIncludeProprietaryBits)' == ''">False</XAIncludeProprietaryBits>
<XABundleToolVersion Condition="'$(XABundleToolVersion)' == ''">0.10.2</XABundleToolVersion>
<XAManifestMergerToolVersion Condition="'$(XAManifestMergerToolVersion)' == ''">26.5.0</XAManifestMergerToolVersion>
<PathSeparator>$([System.IO.Path]::PathSeparator)</PathSeparator>
<_TestsAotName Condition=" '$(AotAssemblies)' == 'true' ">-Aot</_TestsAotName>
<_TestsProfiledAotName Condition=" '$(AndroidEnableProfiledAot)' == 'true' ">-Profiled</_TestsProfiledAotName>
Expand Down
7 changes: 7 additions & 0 deletions Xamarin.Android.sln
Expand Up @@ -101,6 +101,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Android.Tools.Andro
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "r8", "src\r8\r8.csproj", "{1BAFA0CC-0377-46CE-AB7B-7BB2E7B62F63}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "manifestmerger", "src\manifestmerger\manifestmerger.csproj", "{AF8AC493-40AC-4195-82F6-B08EE4B4E49E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "aapt2", "src\aapt2\aapt2.csproj", "{0C31DE30-F9DF-4312-BFFE-DCAD558CCF08}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "bundletool", "src\bundletool\bundletool.csproj", "{A0AEF446-3368-4591-9DE6-BC3B2B33337D}"
Expand Down Expand Up @@ -310,6 +312,10 @@ Global
{1BAFA0CC-0377-46CE-AB7B-7BB2E7B62F63}.Debug|AnyCPU.Build.0 = Debug|Any CPU
{1BAFA0CC-0377-46CE-AB7B-7BB2E7B62F63}.Release|AnyCPU.ActiveCfg = Release|Any CPU
{1BAFA0CC-0377-46CE-AB7B-7BB2E7B62F63}.Release|AnyCPU.Build.0 = Release|Any CPU
{AF8AC493-40AC-4195-82F6-B08EE4B4E49E}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU
{AF8AC493-40AC-4195-82F6-B08EE4B4E49E}.Debug|AnyCPU.Build.0 = Debug|Any CPU
{AF8AC493-40AC-4195-82F6-B08EE4B4E49E}.Release|AnyCPU.ActiveCfg = Release|Any CPU
{AF8AC493-40AC-4195-82F6-B08EE4B4E49E}.Release|AnyCPU.Build.0 = Release|Any CPU
{0C31DE30-F9DF-4312-BFFE-DCAD558CCF08}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU
{0C31DE30-F9DF-4312-BFFE-DCAD558CCF08}.Debug|AnyCPU.Build.0 = Debug|Any CPU
{0C31DE30-F9DF-4312-BFFE-DCAD558CCF08}.Release|AnyCPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -411,6 +417,7 @@ Global
{E34BCFA0-CAA4-412C-AA1C-75DB8D67D157} = {04E3E11E-B47D-4599-8AFC-50515A95E715}
{1E5501E8-49C1-4659-838D-CC9720C5208F} = {CAB438D8-B0F5-4AF0-BEBD-9E2ADBD7B483}
{1BAFA0CC-0377-46CE-AB7B-7BB2E7B62F63} = {04E3E11E-B47D-4599-8AFC-50515A95E715}
{AF8AC493-40AC-4195-82F6-B08EE4B4E49E} = {04E3E11E-B47D-4599-8AFC-50515A95E715}
{0C31DE30-F9DF-4312-BFFE-DCAD558CCF08} = {04E3E11E-B47D-4599-8AFC-50515A95E715}
{A0AEF446-3368-4591-9DE6-BC3B2B33337D} = {04E3E11E-B47D-4599-8AFC-50515A95E715}
{D93CAC27-3893-42A3-99F1-2BCA72E186F4} = {E351F97D-EA4F-4E7F-AAA0-8EBB1F2A4A62}
Expand Down
1 change: 1 addition & 0 deletions build-tools/installers/create-installers.targets
Expand Up @@ -186,6 +186,7 @@
<_MSBuildFiles Include="$(MSBuildSrcDir)\java_runtime_fastdev.jar" />
<_MSBuildFiles Include="$(MSBuildSrcDir)\java_runtime.dex" />
<_MSBuildFiles Include="$(MSBuildSrcDir)\java_runtime_fastdev.dex" />
<_MSBuildFiles Include="$(MSBuildSrcDir)\manifestmerger.jar" />
<_MSBuildFiles Include="$(MSBuildSrcDir)\SgmlReaderDll.dll" />
<_MSBuildFiles Include="$(MSBuildSrcDir)\Xamarin.Android.Aapt.targets" />
<_MSBuildFiles Include="$(MSBuildSrcDir)\Xamarin.Android.Aapt2.targets" />
Expand Down
Expand Up @@ -70,7 +70,6 @@ Copyright (C) 2016 Xamarin. All rights reserved.
ResolvedAssemblies="@(_ResolvedAssemblies)"
ResolvedUserAssemblies="@(_ResolvedUserMonoAndroidAssembliesForDesigner)"
ManifestTemplate="$(_AndroidManifestAbs)"
MergedManifestDocuments="@(ExtractedManifestDocuments)"
Debug="$(AndroidIncludeDebugSymbols)"
NeedsInternet="$(AndroidNeedsInternetPermission)"
AndroidSdkPlatform="$(_AndroidApiLevel)"
Expand All @@ -87,6 +86,15 @@ Copyright (C) 2016 Xamarin. All rights reserved.
AcwMapFile="$(_AcwMapFile)"
SupportedAbis="$(_BuildTargetAbis)">
</GenerateJavaStubs>
<ManifestMerger
ToolPath="$(JavaToolPath)"
JavaOptions="$(JavaOptions)"
ManifestMergerJarPath="$(AndroidManifestMergerJarPath)"
AndroidManifest="$(IntermediateOutputPath)android\AndroidManifest.xml"
OutputManifestFile="$(IntermediateOutputPath)android\AndroidManifest.xml"
LibraryManifestFiles="@(ExtractedManifestDocuments)"
ManifestPlaceholders="$(AndroidManifestPlaceholders)"
/>
<ConvertCustomView
Condition="Exists('$(_CustomViewMapFile)')"
CustomViewMapFile="$(_CustomViewMapFile)"
Expand Down
3 changes: 1 addition & 2 deletions src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs
Expand Up @@ -43,7 +43,6 @@ public class GenerateJavaStubs : AndroidTask
public string [] SupportedAbis { get; set; }

public string ManifestTemplate { get; set; }
public string[] MergedManifestDocuments { get; set; }

public bool Debug { get; set; }
public bool MultiDex { get; set; }
Expand Down Expand Up @@ -255,7 +254,7 @@ void Run (DirectoryAssemblyResolver res)
manifest.NeedsInternet = NeedsInternet;
manifest.InstantRunEnabled = InstantRunEnabled;

var additionalProviders = manifest.Merge (all_java_types, ApplicationJavaClass, EmbedAssemblies, BundledWearApplicationName, MergedManifestDocuments);
var additionalProviders = manifest.Merge (all_java_types, ApplicationJavaClass, EmbedAssemblies, BundledWearApplicationName);

using (var stream = new MemoryStream ()) {
manifest.Save (stream);
Expand Down
103 changes: 103 additions & 0 deletions src/Xamarin.Android.Build.Tasks/Tasks/ManifestMerger.cs
@@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

namespace Xamarin.Android.Tasks
{
public class ManifestMerger : JavaToolTask
{
public override string TaskPrefix => "AMM";

public override string DefaultErrorCode => $"{TaskPrefix}0000";

[Required]
public string ManifestMergerJarPath { get; set; }

[Required]
public string AndroidManifest { get; set; }

[Required]
public string OutputManifestFile { get; set; }

public string [] LibraryManifestFiles { get; set; }

public string [] ManifestPlaceholders { get; set; }

string tempFile;
string responseFile;

public override bool Execute ()
{
tempFile = OutputManifestFile + ".tmp";
responseFile = Path.Combine (Path.GetDirectoryName (OutputManifestFile), "manifestmerger.rsp");
try {
bool result = base.Execute ();
if (!result)
return result;
var m = new ManifestDocument (tempFile, Log);
using (var ms = new MemoryStream ()) {
m.Save (ms);
MonoAndroidHelper.CopyIfStreamChanged (ms, OutputManifestFile);
return result;
}
} finally {
if (File.Exists (tempFile))
File.Delete (tempFile);
if (File.Exists (responseFile))
File.Delete (responseFile);
}
}

protected override string GenerateCommandLineCommands ()
{
string cmd = GetCommandLineBuilder ().ToString ();
Log.LogDebugMessage (cmd);
return cmd;
}

protected virtual CommandLineBuilder GetCommandLineBuilder ()
{
var cmd = new CommandLineBuilder ();

if (!string.IsNullOrEmpty (JavaOptions)) {
cmd.AppendSwitch (JavaOptions);
}
cmd.AppendSwitchIfNotNull ("-Xmx", JavaMaximumHeapSize);
cmd.AppendSwitchIfNotNull ("-cp ", $"{ManifestMergerJarPath}");
cmd.AppendSwitch ("com.xamarin.manifestmerger.Main");
StringBuilder sb = new StringBuilder ();
sb.AppendLine ("--main");
sb.AppendLine (AndroidManifest);
if (LibraryManifestFiles != null) {
sb.AppendLine ("--libs");
sb.AppendLine ($"{string.Join ($"{Path.PathSeparator}", LibraryManifestFiles)}");
}
if (ManifestPlaceholders != null) {
foreach (var entry in ManifestPlaceholders.Select (e => e.Split (new char [] { '=' }, 2, StringSplitOptions.None))) {
if (entry.Length == 2) {
sb.AppendLine ("--placeholder");
sb.AppendLine ($"{entry [0]}={entry [1]}");
} else
Log.LogWarning ("Invalid application placeholders (AndroidApplicationPlaceholders) value. Use 'key1=value1;key2=value2, ...' format. The specified value was: " + ManifestPlaceholders);
}
}
sb.AppendLine ("--out");
sb.AppendLine (tempFile);
File.WriteAllText (responseFile, sb.ToString ());
cmd.AppendFileNameIfNotNull (responseFile);
return cmd;
}

protected override void LogEventsFromTextOutput (string singleLine, MessageImportance messageImportance)
{
if (ExitCode != 0)
Log.LogCodedError (DefaultErrorCode, singleLine);
base.LogEventsFromTextOutput (singleLine, messageImportance);
}
}
}
Expand Up @@ -316,6 +316,12 @@ public void BindingCheckHiddenFiles ([Values (true, false)] bool useShortFileNam
var proj = new XamarinAndroidApplicationProject ();
proj.OtherBuildItems.Add (new BuildItem ("ProjectReference", "..\\Binding\\UnnamedProject.csproj"));
proj.SetProperty (proj.ActiveConfigurationProperties, "UseShortFileNames", useShortFileNames);
proj.AndroidManifest = $@"<?xml version=""1.0"" encoding=""utf-8""?>
<manifest xmlns:android=""http://schemas.android.com/apk/res/android"" xmlns:tools=""http://schemas.android.com/tools"" android:versionCode=""1"" android:versionName=""1.0"" package=""{proj.PackageName}"">
<uses-sdk />
<application android:label=""{proj.ProjectName}"" tools:replace=""android:label"">
</application>
</manifest>";
using (var b = CreateApkBuilder (Path.Combine (path, "App"))) {
Assert.IsTrue (b.Build (proj), "Build should have succeeded.");
var assemblyMap = b.Output.GetIntermediaryPath (Path.Combine ("lp", "map.cache"));
Expand Down
Expand Up @@ -7,7 +7,9 @@
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
using Microsoft.Build.Framework;
using Mono.Cecil;
using NUnit.Framework;
Expand Down Expand Up @@ -1718,7 +1720,7 @@ public void CheckLintErrorsAndWarnings ()
{
FixLintOnWindows ();

string disabledIssues = "StaticFieldLeak,ObsoleteSdkInt,AllowBackup";
string disabledIssues = "StaticFieldLeak,ObsoleteSdkInt,AllowBackup,ExportedReceiver";

var proj = new XamarinAndroidApplicationProject () {
PackageReferences = {
Expand Down Expand Up @@ -2319,11 +2321,14 @@ public void ApplicationIdPlaceholder ()
{
var proj = new XamarinAndroidApplicationProject ();
proj.AndroidManifest = proj.AndroidManifest.Replace ("</application>", "<provider android:name='${applicationId}' android:authorities='example' /></application>");
var builder = CreateApkBuilder ("temp/ApplicationIdPlaceholder");
builder.Build (proj);
var appsrc = File.ReadAllText (Path.Combine (Root, builder.ProjectDirectory, "obj", "Debug", "android", "AndroidManifest.xml"));
Assert.IsTrue (appsrc.Contains ("<provider android:name=\"UnnamedProject.UnnamedProject\""), "placeholder not replaced");
builder.Dispose ();
using (var builder = CreateApkBuilder ("temp/ApplicationIdPlaceholder")) {
builder.Build (proj);
var manifest = XDocument.Load (Path.Combine (Root, builder.ProjectDirectory, "obj", "Debug", "android", "AndroidManifest.xml"));
var namespaceResolver = new XmlNamespaceManager (new NameTable ());
namespaceResolver.AddNamespace ("android", "http://schemas.android.com/apk/res/android");
var element = manifest.XPathSelectElement ("/manifest/application/provider[@android:name='UnnamedProject.UnnamedProject']", namespaceResolver);
Assert.IsNotNull (element, "placeholder not replaced");
}
}

[Test]
Expand Down
Expand Up @@ -453,7 +453,7 @@ public void ManifestPlaceholders ()
using (var builder = CreateApkBuilder (Path.Combine ("temp", TestContext.CurrentContext.Test.Name), false, false)) {
builder.Build (proj);
var manifest = builder.Output.GetIntermediaryAsText (Root, Path.Combine ("android", "AndroidManifest.xml"));
Assert.IsTrue (manifest.Contains ("application android:label=\"val1\""), "#1");
Assert.IsTrue (manifest.Contains (" android:label=\"val1\""), "#1");
Assert.IsTrue (manifest.Contains (" x=\"a=b\\c\"".Replace ('\\', Path.DirectorySeparatorChar)), "#2");
}
}
Expand Down Expand Up @@ -659,7 +659,7 @@ public class TestActivity2 : FragmentActivity {
[Test]
public void AllActivityAttributeProperties ()
{
const string expectedOutput = "android:allowEmbedded=\"true\" android:allowTaskReparenting=\"true\" android:alwaysRetainTaskState=\"true\" android:autoRemoveFromRecents=\"true\" android:banner=\"@drawable/icon\" android:clearTaskOnLaunch=\"true\" android:colorMode=\"hdr\" android:configChanges=\"mcc\" android:description=\"@string/app_name\" android:directBootAware=\"true\" android:documentLaunchMode=\"never\" android:enabled=\"true\" android:enableVrMode=\"foo\" android:excludeFromRecents=\"true\" android:exported=\"true\" android:finishOnCloseSystemDialogs=\"true\" android:finishOnTaskLaunch=\"true\" android:hardwareAccelerated=\"true\" android:icon=\"@drawable/icon\" android:immersive=\"true\" android:label=\"TestActivity\" android:launchMode=\"singleTop\" android:lockTaskMode=\"normal\" android:logo=\"@drawable/icon\" android:maxAspectRatio=\"1.2\" android:maxRecents=\"1\" android:multiprocess=\"true\" android:name=\"com.contoso.TestActivity\" android:noHistory=\"true\" android:parentActivityName=\"unnamedproject.unnamedproject.MainActivity\" android:permission=\"com.contoso.permission.TEST_ACTIVITY\" android:persistableMode=\"persistNever\" android:process=\"com.contoso.process.testactivity_process\" android:recreateOnConfigChanges=\"mcc\" android:relinquishTaskIdentity=\"true\" android:resizeableActivity=\"true\" android:resumeWhilePausing=\"true\" android:rotationAnimation=\"crossfade\" android:roundIcon=\"@drawable/icon\" android:screenOrientation=\"portrait\" android:showForAllUsers=\"true\" android:showOnLockScreen=\"true\" android:showWhenLocked=\"true\" android:singleUser=\"true\" android:stateNotNeeded=\"true\" android:supportsPictureInPicture=\"true\" android:taskAffinity=\"com.contoso\" android:theme=\"@android:style/Theme.Light\" android:turnScreenOn=\"true\" android:uiOptions=\"splitActionBarWhenNarrow\" android:visibleToInstantApps=\"true\" android:windowSoftInputMode=\"stateUnchanged|adjustUnspecified\"";
const string expectedOutput = "android:name=\"com.contoso.TestActivity\" android:allowEmbedded=\"true\" android:allowTaskReparenting=\"true\" android:alwaysRetainTaskState=\"true\" android:autoRemoveFromRecents=\"true\" android:banner=\"@drawable/icon\" android:clearTaskOnLaunch=\"true\" android:colorMode=\"hdr\" android:configChanges=\"mcc\" android:description=\"@string/app_name\" android:directBootAware=\"true\" android:documentLaunchMode=\"never\" android:enableVrMode=\"foo\" android:enabled=\"true\" android:excludeFromRecents=\"true\" android:exported=\"true\" android:finishOnCloseSystemDialogs=\"true\" android:finishOnTaskLaunch=\"true\" android:hardwareAccelerated=\"true\" android:icon=\"@drawable/icon\" android:immersive=\"true\" android:label=\"TestActivity\" android:launchMode=\"singleTop\" android:lockTaskMode=\"normal\" android:logo=\"@drawable/icon\" android:maxAspectRatio=\"1.2\" android:maxRecents=\"1\" android:multiprocess=\"true\" android:noHistory=\"true\" android:parentActivityName=\"unnamedproject.unnamedproject.MainActivity\" android:permission=\"com.contoso.permission.TEST_ACTIVITY\" android:persistableMode=\"persistNever\" android:process=\"com.contoso.process.testactivity_process\" android:recreateOnConfigChanges=\"mcc\" android:relinquishTaskIdentity=\"true\" android:resizeableActivity=\"true\" android:resumeWhilePausing=\"true\" android:rotationAnimation=\"crossfade\" android:roundIcon=\"@drawable/icon\" android:screenOrientation=\"portrait\" android:showForAllUsers=\"true\" android:showOnLockScreen=\"true\" android:showWhenLocked=\"true\" android:singleUser=\"true\" android:stateNotNeeded=\"true\" android:supportsPictureInPicture=\"true\" android:taskAffinity=\"com.contoso\" android:theme=\"@android:style/Theme.Light\" android:turnScreenOn=\"true\" android:uiOptions=\"splitActionBarWhenNarrow\" android:visibleToInstantApps=\"true\" android:windowSoftInputMode=\"stateUnchanged|adjustUnspecified\"";

var proj = new XamarinAndroidApplicationProject ();

Expand Down Expand Up @@ -739,7 +739,7 @@ class TestActivity : Activity { }"
[Test]
public void AllServiceAttributeProperties ()
{
const string expectedOutput = "android:directBootAware=\"true\" android:enabled=\"true\" android:exported=\"true\" android:foregroundServiceType=\"connectedDevice\" android:icon=\"@drawable/icon\" android:isolatedProcess=\"true\" android:label=\"TestActivity\" android:name=\"com.contoso.TestActivity\" android:permission=\"com.contoso.permission.TEST_ACTIVITY\" android:process=\"com.contoso.process.testactivity_process\" android:roundIcon=\"@drawable/icon\"";
const string expectedOutput = "android:name=\"com.contoso.TestActivity\" android:directBootAware=\"true\" android:enabled=\"true\" android:exported=\"true\" android:foregroundServiceType=\"connectedDevice\" android:icon=\"@drawable/icon\" android:isolatedProcess=\"true\" android:label=\"TestActivity\" android:permission=\"com.contoso.permission.TEST_ACTIVITY\" android:process=\"com.contoso.process.testactivity_process\" android:roundIcon=\"@drawable/icon\"";

var proj = new XamarinAndroidApplicationProject ();

Expand Down
@@ -1,6 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="${PACKAGENAME}">
<uses-sdk />
<application android:label="${PROJECT_NAME}">
</application>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:versionCode="1"
android:versionName="1.0"
package="${PACKAGENAME}"
>
<uses-sdk />
<application android:label="${PROJECT_NAME}">
</application>
</manifest>

0 comments on commit 2c6f5cd

Please sign in to comment.