diff --git a/.gitmodules b/.gitmodules index 17af4eba7c0..48f228fa79b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -41,6 +41,9 @@ path = external/xamarin-android-tools url = https://github.com/xamarin/xamarin-android-tools branch = master +[submodule "external/r8"] + path = external/r8 + url = https://r8.googlesource.com/r8 [submodule "external/dlfcn-win32"] path = external/dlfcn-win32 url = https://github.com/dlfcn-win32/dlfcn-win32.git diff --git a/Documentation/guides/BuildProcess.md b/Documentation/guides/BuildProcess.md index deca390dcd6..b2c0f1c9185 100644 --- a/Documentation/guides/BuildProcess.md +++ b/Documentation/guides/BuildProcess.md @@ -210,6 +210,23 @@ when packaing Release applications. This property is `False` by default. +- **AndroidDexGenerator** – An enum-style property with valid + values of `dx` or `d8`. Indicates which Android [dex][dex] + compiler is used during the Xamarin.Android build process. + Currently defaults to `dx`. For further information see our + documentation on [D8 and R8][d8-r8]. + + [dex]: https://source.android.com/devices/tech/dalvik/dalvik-bytecode + [d8-r8]: D8andR8.md + +- **AndroidEnableDesugar** – A boolean property that + determines if `desugar` is enabled. Android does not currently + support all Java 8 features, and the default toolchain implements + the new language features by performing bytecode transformations, + called `desugar`, on the output of the `javac` compiler. Defaults + to `False` if using `AndroidDexGenerator=dx` and defaults to + `True` if using `AndroidDexGenerator=d8`. + - **AndroidEnableMultiDex** – A boolean property that determines whether or not multi-dex support will be used in the final `.apk`. @@ -360,6 +377,14 @@ when packaing Release applications. Assembly1;Assembly2 ``` +- **AndroidLinkTool** – An enum-style property with valid + values of `proguard` or `d8`. Indicates which code shrinker is + used for Java code. Currently defaults to blank, or `proguard` if + `$(AndroidEnableProguard)` is `True`. For further information see + our documentation on [D8 and R8][d8-r8]. + + [d8-r8]: D8andR8.md + - **LinkerDumpDependencies** – A bool property which enables generating of linker dependencies file. This file can be used as input for diff --git a/Documentation/guides/D8andR8.md b/Documentation/guides/D8andR8.md new file mode 100644 index 00000000000..9405e6eec2f --- /dev/null +++ b/Documentation/guides/D8andR8.md @@ -0,0 +1,243 @@ +This is the D8 and R8 integration specification for Xamarin.Android. + +# What is D8? What is R8? + +At a high level, here are the steps that occur during an Android +application's Java compilation: +- `javac` compiles Java code +- `desugar` remove's the "sugar" (from Java 8 features) that are not + fully supported on Android +- `proguard` shrinks compiled Java code +- `dx` "dexes" compiled Java code into Android [dex][dex] format. This + is an alternate Java bytecode format supported by the Android + platform. + +This process has a few issues, such as: +- [proguard](https://www.guardsquare.com/en/products/proguard/manual) + is made by a third party, and aimed for Java in general (not Android + specific) +- `dx` is slower than it _could_ be + +So in 2017, Google announced a "next-generation" dex compiler named +[D8](https://android-developers.googleblog.com/2017/08/next-generation-dex-compiler-now-in.html). + +- D8 is a direct replacement for `dx` +- R8 is a replacement for `proguard`, that also "dexes" at the same + time. If using R8, a D8 call is not needed. + +Both tools have support for various other Android-specifics: +- Both `desugar` by default unless the `--no-desugaring` switch is + specified +- Both support [multidex][multidex] +- R8 additionally has support to generate a default `multidex.keep` + file, that `proguard` can generate + +Additionally, R8 is geared to be backwards compatible to `proguard`. +It uses the same file format for configuration and command-line +parameters as `proguard`. However, at the time of writing this, there +are still several flags/features not implemented in R8 yet. + +For more information on how R8 compares to `proguard`, see a great +article written by the `proguard` team +[here](https://www.guardsquare.com/en/blog/proguard-and-r8). + +You can find the source for D8 and R8 +[here](https://r8.googlesource.com/r8/). + +For reference, `d8 --help`: +``` +Usage: d8 [options] + where are any combination of dex, class, zip, jar, or apk files + and options are: + --debug # Compile with debugging information (default). + --release # Compile without debugging information. + --output # Output result in . + # must be an existing directory or a zip file. + --lib # Add as a library resource. + --classpath # Add as a classpath resource. + --min-api # Minimum Android API level compatibility + --intermediate # Compile an intermediate result intended for later + # merging. + --file-per-class # Produce a separate dex file per input class + --no-desugaring # Force disable desugaring. + --main-dex-list # List of classes to place in the primary dex file. + --version # Print the version of d8. + --help # Print this message. +``` + +For reference, `r8 --help`: +``` +Usage: r8 [options] + where are any combination of dex, class, zip, jar, or apk files + and options are: + --release # Compile without debugging information (default). + --debug # Compile with debugging information. + --output # Output result in . + # must be an existing directory or a zip file. + --lib # Add as a library resource. + --min-api # Minimum Android API level compatibility. + --pg-conf # Proguard configuration . + --pg-map-output # Output the resulting name and line mapping to . + --no-tree-shaking # Force disable tree shaking of unreachable classes. + --no-minification # Force disable minification of names. + --no-desugaring # Force disable desugaring. + --main-dex-rules # Proguard keep rules for classes to place in the + # primary dex file. + --main-dex-list # List of classes to place in the primary dex file. + --main-dex-list-output # Output the full main-dex list in . + --version # Print the version of r8. + --help # Print this message. +``` + +# What did Xamarin.Android do *before* D8/R8? + +In other words, what is currently happening *before* we introduce D8/R8 support? + +1. The [Javac](https://github.com/xamarin/xamarin-android/blob/221a2190ebb3aaec9ecd9b1cf8f7f6174c43153a/src/Xamarin.Android.Build.Tasks/Tasks/Javac.cs) + MSBuild task compiles `*.java` files to a `classes.zip` file. +2. The [Desugar](https://github.com/xamarin/xamarin-android/blob/221a2190ebb3aaec9ecd9b1cf8f7f6174c43153a/src/Xamarin.Android.Build.Tasks/Tasks/Desugar.cs) + MSBuild task "desugars" using `desugar_deploy.jar` if + `$(AndroidEnableDesugar)` is `True`. +3. The [Proguard](https://github.com/xamarin/xamarin-android/blob/221a2190ebb3aaec9ecd9b1cf8f7f6174c43153a/src/Xamarin.Android.Build.Tasks/Tasks/Proguard.cs) + MSBuild task shrinks the compiled Java code if + `$(AndroidEnableProguard)` is `True`. Developers may also supply + custom proguard configuration files via `ProguardConfiguration` + build items. +4. The [CreateMultiDexMainDexClassList](https://github.com/xamarin/xamarin-android/blob/221a2190ebb3aaec9ecd9b1cf8f7f6174c43153a/src/Xamarin.Android.Build.Tasks/Tasks/CreateMultiDexMainDexClassList.cs) + MSBuild task runs `proguard` to generate a final, combined + `multidex.keep` file if `$(AndroidEnableMultiDex)` is `True`. + Developers can also supply custom `multidex.keep` files via + `MultiDexMainDexList` build items. +5. The [CompileToDalvik](https://github.com/xamarin/xamarin-android/blob/221a2190ebb3aaec9ecd9b1cf8f7f6174c43153a/src/Xamarin.Android.Build.Tasks/Tasks/CompileToDalvik.cs) + MSBuild task runs `dx.jar` to generate a final `classes.dex` file + in `$(IntermediateOutputPath)android\bin`. If `multidex` is + enabled, a `classes2.dex` (and potentially more) are also generated + in this location. + +# What would this process look like with D8 / R8? + +Two new MSBuild tasks named `R8` and `D8` will be created. + +1. The [Javac](https://github.com/xamarin/xamarin-android/blob/221a2190ebb3aaec9ecd9b1cf8f7f6174c43153a/src/Xamarin.Android.Build.Tasks/Tasks/Javac.cs) + MSBuild task will remain unchanged. +2. `R8` will be invoked to create a `multidex.keep` file if + `$(AndroidEnableMultiDex)` is `True`. +3. `D8` will run if `$(AndroidEnableProguard)` is `False` and + "desugar" by default. +4. Otherwise, `R8` will run if `$(AndroidEnableProguard)` is `True` + and will also "desugar" by default. + +So in addition to be being faster (if Google's claims are true), we +will be calling less tooling to accomplish the same results. + +# So how do developers use it? What are sensible MSBuild property defaults? + +Currently, a `csproj` file might have the following properties: +```xml + + + True + True + True + + +``` + +To enable the new behavior, we should introduce two new enum-style +properties: +- `$(AndroidDexGenerator)` - supports `dx` or `d8` +- `$(AndroidLinkTool)` - supports `proguard` or `r8` + +But for an existing project, a developer could opt-in to the new +behavior with two properties: +```xml + + + True + True + True + + d8 + r8 + + +``` + +There should be two new MSBuild properties to configure here, because: +- You could use `D8` in combination with `proguard`, as `R8` is not + "feature complete" in comparison to `proguard`. +- You may not want to use code shrinking at all, but still use `D8` + instead of `dx`. +- You shouldn't be able to use `dx` in combination with `R8`, it + doesn't make sense. +- Developers should be able to use the existing properties for + enabling code shrinking, `multidex`, and `desugar`. + +Our reasonable defaults would be: +- If `AndroidDexGenerator` is omitted, `dx` and `CompileToDalvik` + should be used. Until D8/R8 integration is deemed stable and enabled + by default. +- If `AndroidDexGenerator` is `d8` and `AndroidEnableDesugar` is + omitted, `AndroidEnableDesugar` should be enabled. +- If `AndroidLinkTool` is omitted and `AndroidEnableProguard` is + `true`, we should default `AndroidLinkTool` to `proguard`. + +MSBuild properties default to something like: +```xml +dx + +proguard +True +``` + +If a user specifies combinations of properties: +- `AndroidDexGenerator` = `d8` and `AndroidEnableProguard` = `True` + - `AndroidLinkTool` will get set to `proguard` +- `AndroidDexGenerator` = `dx` and `AndroidLinkTool` = `r8` + - This combination doesn't really *make sense*, but we don't need to + do anything: only `R8` will be called because it dexes and shrinks + at the same time. +- `AndroidEnableDesugar` is enabled when omitted, if either `d8` or + `r8` are used + +For new projects that want to use D8/R8, code shrinking, and +`multidex`, it would make sense to specify: +```xml + + + True + d8 + r8 + + +``` + +# Additional D8 / R8 settings? + +`--debug` or `--release` needs to be explicitly specified for both D8 +and R8. We should use the [AndroidIncludeDebugSymbols][debug_symbols] +property for this. + +`$(D8ExtraArguments)` and `$(R8ExtraArguments)` can be used to +explicitly pass additional flags to D8 and R8. + +# How are we compiling / shipping D8 and R8? + +We will add a submodule to `xamarin-android` for +[r8](https://r8.googlesource.com/r8/). It should be pinned to a commit +with a reasonable release tag, such as `1.2.35` for now. + +To build r8, we have to: +- Download and unzip a tool named [depot_tools][depot_tools] from the + Chromium project +- Put the path to `depot_tools` in `$PATH` +- Run `gclient` so it will download/bootstrap gradle, python, and + other tools +- Run `python tools\gradle.py d8 r8` to compile `d8.jar` and `r8.jar` +- We will need to ship `d8.jar` and `r8.jar` in our installers, + similar to how we are shipping `desugar_deploy.jar` + +[dex]: https://source.android.com/devices/tech/dalvik/dalvik-bytecode +[multidex]: https://developer.android.com/studio/build/multidex +[debug_symbols]: https://github.com/xamarin/xamarin-android/blob/221a2190ebb3aaec9ecd9b1cf8f7f6174c43153a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets#L315-L336 +[depot_tools]: http://commondatastorage.googleapis.com/chrome-infra-docs/flat/depot_tools/docs/html/depot_tools_tutorial.html diff --git a/Documentation/guides/messages/xa4304.md b/Documentation/guides/messages/xa4304.md index 384c2b38478..f554ea1c2b4 100644 --- a/Documentation/guides/messages/xa4304.md +++ b/Documentation/guides/messages/xa4304.md @@ -1,4 +1,4 @@ -# Compiler Error XA4304 +# Compiler Warning XA4304 The `Proguard` MSBuild task encountered a proguard configuration file that was not found on disk. These files are generally declared in your diff --git a/Documentation/guides/messages/xa4305.md b/Documentation/guides/messages/xa4305.md new file mode 100644 index 00000000000..bb6036ba6fb --- /dev/null +++ b/Documentation/guides/messages/xa4305.md @@ -0,0 +1,21 @@ +# Compiler Error/Warning XA4305 + +The `CreateMultiDexMainDexClassList`, `CompileToDalvik` or `R8` +MSBuild task encountered a `multidex.keep` file that was not found on +disk. You can customize `multidex` settings for your Xamarin.Android +application by adding files with the `MultiDexMainDexList` build item, +which are combined into a final `multidex.keep` file. + +To learn more about `multidex` and how it relates to Android +development, see the [Android documentation][android]. + +## Resolution + +Verify you are not declaring a `MultiDexMainDexList` build item that +does not exist. + +Consider submitting a [bug][bug] if you are getting this error/warning +under normal circumstances. + +[android]: https://developer.android.com/studio/build/multidex +[bug]: https://github.com/xamarin/xamarin-android/wiki/Submitting-Bugs,-Feature-Requests,-and-Pull-Requests diff --git a/Xamarin.Android.sln b/Xamarin.Android.sln index b53520fd5ea..89e51cdc8ce 100644 --- a/Xamarin.Android.sln +++ b/Xamarin.Android.sln @@ -123,6 +123,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Android.Tools.Andro EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Android.Tools.AndroidSdk-Tests", "external\xamarin-android-tools\src\Xamarin.Android.Tools.AndroidSdk\Tests\Xamarin.Android.Tools.AndroidSdk-Tests.csproj", "{1E5501E8-49C1-4659-838D-CC9720C5208F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "r8", "src\r8\r8.csproj", "{1BAFA0CC-0377-46CE-AB7B-7BB2E7B62F63}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "proprietary", "build-tools\proprietary\proprietary.csproj", "{D93CAC27-3893-42A3-99F1-2BCA72E186F4}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "download-bundle", "build-tools\download-bundle\download-bundle.csproj", "{1DA0CB12-5508-4E83-A242-0C8D6D99A49B}" @@ -358,6 +360,10 @@ Global {1DA0CB12-5508-4E83-A242-0C8D6D99A49B}.Debug|AnyCPU.Build.0 = Debug|Any CPU {1DA0CB12-5508-4E83-A242-0C8D6D99A49B}.Release|AnyCPU.ActiveCfg = Release|Any CPU {1DA0CB12-5508-4E83-A242-0C8D6D99A49B}.Release|AnyCPU.Build.0 = Release|Any CPU + {1BAFA0CC-0377-46CE-AB7B-7BB2E7B62F63}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU + {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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -418,8 +424,7 @@ Global {B8105878-D423-4159-A3E7-028298281EC6} = {04E3E11E-B47D-4599-8AFC-50515A95E715} {E34BCFA0-CAA4-412C-AA1C-75DB8D67D157} = {04E3E11E-B47D-4599-8AFC-50515A95E715} {1E5501E8-49C1-4659-838D-CC9720C5208F} = {CAB438D8-B0F5-4AF0-BEBD-9E2ADBD7B483} - {D93CAC27-3893-42A3-99F1-2BCA72E186F4} = {E351F97D-EA4F-4E7F-AAA0-8EBB1F2A4A62} - {1DA0CB12-5508-4E83-A242-0C8D6D99A49B} = {E351F97D-EA4F-4E7F-AAA0-8EBB1F2A4A62} + {1BAFA0CC-0377-46CE-AB7B-7BB2E7B62F63} = {04E3E11E-B47D-4599-8AFC-50515A95E715} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {53A1F287-EFB2-4D97-A4BB-4A5E145613F6} diff --git a/external/r8 b/external/r8 new file mode 160000 index 00000000000..671655efb15 --- /dev/null +++ b/external/r8 @@ -0,0 +1 @@ +Subproject commit 671655efb1567746e11b7f89c3c1c2a3c6283f8b diff --git a/external/r8.tpnitems b/external/r8.tpnitems new file mode 100644 index 00000000000..8d945808d81 --- /dev/null +++ b/external/r8.tpnitems @@ -0,0 +1,9 @@ + + + + + $(MSBuildThisFileDirectory)\r8\LICENSE + https://r8.googlesource.com/r8/ + + + diff --git a/src/Mono.Android/Test/Mono.Android-Tests.csproj b/src/Mono.Android/Test/Mono.Android-Tests.csproj index 001a4bf5194..931302e7ad2 100644 --- a/src/Mono.Android/Test/Mono.Android-Tests.csproj +++ b/src/Mono.Android/Test/Mono.Android-Tests.csproj @@ -20,6 +20,7 @@ False v8.1 true + d8 @@ -41,6 +42,7 @@ 4 false true + r8 diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CompileToDalvik.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CompileToDalvik.cs index c516a5187bc..c862b2df774 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/CompileToDalvik.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/CompileToDalvik.cs @@ -116,8 +116,14 @@ protected override string GenerateCommandLineCommands () cmd.AppendSwitchIfNotNull ("--input-list=", inputListFile); if (MultiDexEnabled) { - cmd.AppendSwitch ("--multi-dex"); - cmd.AppendSwitchIfNotNull ("--main-dex-list=", MultiDexMainDexListFile); + if (string.IsNullOrEmpty (MultiDexMainDexListFile)) { + Log.LogCodedWarning ("XA4305", $"MultiDex is enabled, but '{nameof (MultiDexMainDexListFile)}' was not specified."); + } else if (!File.Exists (MultiDexMainDexListFile)) { + Log.LogCodedWarning ("XA4305", MultiDexMainDexListFile, 0, $"MultiDex is enabled, but main dex list file '{MultiDexMainDexListFile}' does not exist."); + } else { + cmd.AppendSwitch ("--multi-dex"); + cmd.AppendSwitchIfNotNull ("--main-dex-list=", MultiDexMainDexListFile); + } } cmd.AppendSwitchIfNotNull ("--output ", Path.GetDirectoryName (ClassesOutputDirectory)); diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CreateMultiDexMainDexClassList.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CreateMultiDexMainDexClassList.cs index a138f28a91c..5fd5fe9e400 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/CreateMultiDexMainDexClassList.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/CreateMultiDexMainDexClassList.cs @@ -62,9 +62,17 @@ public override bool Execute () var result = base.Execute () && !Log.HasLoggedErrors; - if (result && CustomMainDexListFiles != null && CustomMainDexListFiles.Any (x => File.Exists (x.ItemSpec))) { - foreach (var content in CustomMainDexListFiles.Select (i => File.ReadAllLines (i.ItemSpec))) - File.AppendAllLines (MultiDexMainDexListFile, content); + if (result && CustomMainDexListFiles?.Length > 0) { + var content = new List (); + foreach (var file in CustomMainDexListFiles) { + if (File.Exists (file.ItemSpec)) { + content.Add (File.ReadAllText (file.ItemSpec)); + } else { + Log.LogCodedError ("XA4305", file.ItemSpec, 0, $"'MultiDexMainDexList' file '{file.ItemSpec}' does not exist."); + } + } + File.WriteAllText (MultiDexMainDexListFile, string.Concat (content)); + return true; } return result; diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/D8.cs b/src/Xamarin.Android.Build.Tasks/Tasks/D8.cs new file mode 100644 index 00000000000..c4bc6a0e2c6 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tasks/D8.cs @@ -0,0 +1,97 @@ +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tasks +{ + public class D8 : JavaToolTask + { + [Required] + public string JarPath { get; set; } + + [Required] + public string OutputDirectory { get; set; } + + // It is loaded to calculate --min-api, which is used by desugaring part to determine which levels of desugaring it performs. + [Required] + public string AndroidManifestFile { get; set; } + + // general d8 feature options. + public bool Debug { get; set; } + public bool EnableDesugar { get; set; } + + // Java libraries to embed or reference + [Required] + public string ClassesZip { get; set; } + [Required] + public string JavaPlatformJarPath { get; set; } + public ITaskItem [] JavaLibrariesToEmbed { get; set; } + public ITaskItem [] JavaLibrariesToReference { get; set; } + + // multidex + public bool EnableMultiDex { get; set; } + public string MultiDexMainDexListFile { get; set; } + + public string ExtraArguments { get; set; } + + protected override string GenerateCommandLineCommands () + { + return GetCommandLineBuilder ().ToString (); + } + + protected virtual CommandLineBuilder GetCommandLineBuilder () + { + var cmd = new CommandLineBuilder (); + + cmd.AppendSwitchIfNotNull ("-jar ", JarPath); + + if (!string.IsNullOrEmpty (ExtraArguments)) + cmd.AppendSwitch (ExtraArguments); // it should contain "--dex". + if (Debug) + cmd.AppendSwitch ("--debug"); + else + cmd.AppendSwitch ("--release"); + + // multidexing + if (EnableMultiDex) { + if (string.IsNullOrEmpty (MultiDexMainDexListFile)) { + Log.LogCodedWarning ("XA4305", $"MultiDex is enabled, but '{nameof (MultiDexMainDexListFile)}' was not specified."); + } else if (!File.Exists (MultiDexMainDexListFile)) { + Log.LogCodedWarning ("XA4305", MultiDexMainDexListFile, 0, $"MultiDex is enabled, but main dex list file '{MultiDexMainDexListFile}' does not exist."); + } else { + cmd.AppendSwitchIfNotNull ("--main-dex-list ", MultiDexMainDexListFile); + } + } + + // desugaring + var doc = AndroidAppManifest.Load (AndroidManifestFile, MonoAndroidHelper.SupportedVersions); + int minApiVersion = doc.MinSdkVersion == null ? 4 : (int)doc.MinSdkVersion; + cmd.AppendSwitchIfNotNull ("--min-api ", minApiVersion.ToString ()); + + if (!EnableDesugar) + cmd.AppendSwitch ("--no-desugaring"); + + var injars = new List (); + var libjars = new List (); + injars.Add (ClassesZip); + if (JavaLibrariesToEmbed != null) + foreach (var jarfile in JavaLibrariesToEmbed) + injars.Add (jarfile.ItemSpec); + libjars.Add (JavaPlatformJarPath); + if (JavaLibrariesToReference != null) + foreach (var jarfile in JavaLibrariesToReference.Select (p => p.ItemSpec)) + libjars.Add (jarfile); + + cmd.AppendSwitchIfNotNull ("--output ", OutputDirectory); + foreach (var jar in libjars) + cmd.AppendSwitchIfNotNull ("--lib ", jar); + foreach (var jar in injars) + cmd.AppendFileNameIfNotNull (jar); + + return cmd; + } + } +} \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/Proguard.cs b/src/Xamarin.Android.Build.Tasks/Tasks/Proguard.cs index 3ed19e8afc4..744b7f683e6 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/Proguard.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/Proguard.cs @@ -41,23 +41,17 @@ public class Proguard : ToolTask [Required] public string ProguardJarOutput { get; set; } - [Required] public string ProguardGeneratedReferenceConfiguration { get; set; } - - [Required] public string ProguardGeneratedApplicationConfiguration { get; set; } - - [Required] public string ProguardCommonXamarinConfiguration { get; set; } + [Required] public string ProguardConfigurationFiles { get; set; } public ITaskItem[] JavaLibrariesToEmbed { get; set; } - public ITaskItem[] ExternalJavaLibraries { get; set; } + public ITaskItem[] JavaLibrariesToReference { get; set; } - public ITaskItem[] DoNotPackageJavaLibraries { get; set; } - public bool UseProguard { get; set; } public string JavaOptions { get; set; } @@ -119,15 +113,9 @@ protected override string GenerateCommandLineCommands () // skip invalid lines } - var injars = new List (); - var libjars = new List (); - injars.Add (classesZip); - if (JavaLibrariesToEmbed != null) - foreach (var jarfile in JavaLibrariesToEmbed) - injars.Add (jarfile.ItemSpec); - - using (var xamcfg = File.Create (ProguardCommonXamarinConfiguration)) - GetType ().Assembly.GetManifestResourceStream ("proguard_xamarin.cfg").CopyTo (xamcfg); + if (!string.IsNullOrWhiteSpace (ProguardCommonXamarinConfiguration)) + using (var xamcfg = File.Create (ProguardCommonXamarinConfiguration)) + GetType ().Assembly.GetManifestResourceStream ("proguard_xamarin.cfg").CopyTo (xamcfg); var configs = ProguardConfigurationFiles .Replace ("{sdk.dir}", AndroidSdkDirectory + Path.DirectorySeparatorChar) @@ -148,9 +136,15 @@ protected override string GenerateCommandLineCommands () Log.LogCodedWarning ("XA4304", file, 0, "Proguard configuration file '{0}' was not found.", file); } + var injars = new List (); + var libjars = new List (); + injars.Add (classesZip); + if (JavaLibrariesToEmbed != null) + foreach (var jarfile in JavaLibrariesToEmbed) + injars.Add (jarfile.ItemSpec); libjars.Add (JavaPlatformJarPath); - if (ExternalJavaLibraries != null) - foreach (var jarfile in ExternalJavaLibraries.Select (p => p.ItemSpec)) + if (JavaLibrariesToReference != null) + foreach (var jarfile in JavaLibrariesToReference.Select (p => p.ItemSpec)) libjars.Add (jarfile); cmd.AppendSwitchUnquotedIfNotNull ("-injars ", "\"'" + string.Join ($"'{ProguardInputJarFilter}{Path.PathSeparator}'", injars.Distinct ()) + $"'{ProguardInputJarFilter}\""); diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/R8.cs b/src/Xamarin.Android.Build.Tasks/Tasks/R8.cs new file mode 100644 index 00000000000..3a9bc001758 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tasks/R8.cs @@ -0,0 +1,72 @@ +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using System.IO; +using System.Linq; + +namespace Xamarin.Android.Tasks +{ + public class R8 : D8 + { + // general r8 feature options. + public bool EnableMinify { get; set; } // The Task has the option, but it is not supported at all. + public bool EnableTreeShaking { get; set; } + + // used for proguard configuration settings + [Required] + public string AndroidSdkDirectory { get; set; } + [Required] + public string AcwMapFile { get; set; } + public string ProguardGeneratedReferenceConfiguration { get; set; } + public string ProguardGeneratedApplicationConfiguration { get; set; } + public string ProguardCommonXamarinConfiguration { get; set; } + [Required] + public string ProguardConfigurationFiles { get; set; } + public string ProguardMappingOutput { get; set; } + + protected override CommandLineBuilder GetCommandLineBuilder () + { + var cmd = base.GetCommandLineBuilder (); + + // generating proguard application configuration + if (EnableTreeShaking) { + var acwLines = File.ReadAllLines (AcwMapFile); + using (var appcfg = File.CreateText (ProguardGeneratedApplicationConfiguration)) + for (int i = 0; i + 2 < acwLines.Length; i += 3) + try { + var line = acwLines [i + 2]; + var java = line.Substring (line.IndexOf (';') + 1); + appcfg.WriteLine ("-keep class " + java + " { *; }"); + } catch { + // skip invalid lines + } + if (!string.IsNullOrWhiteSpace (ProguardCommonXamarinConfiguration)) + using (var xamcfg = File.Create (ProguardCommonXamarinConfiguration)) + GetType ().Assembly.GetManifestResourceStream ("proguard_xamarin.cfg").CopyTo (xamcfg); + var configs = ProguardConfigurationFiles + .Replace ("{sdk.dir}", AndroidSdkDirectory + Path.DirectorySeparatorChar) + .Replace ("{intermediate.common.xamarin}", ProguardCommonXamarinConfiguration) + .Replace ("{intermediate.references}", ProguardGeneratedReferenceConfiguration) + .Replace ("{intermediate.application}", ProguardGeneratedApplicationConfiguration) + .Replace ("{project}", string.Empty) // current directory anyways. + .Split (';') + .Select (s => s.Trim ()) + .Where (s => !string.IsNullOrWhiteSpace (s)); + foreach (var file in configs) { + if (File.Exists (file)) + cmd.AppendSwitchIfNotNull ("--pg-conf ", file); + else + Log.LogCodedWarning ("XA4304", file, 0, "Proguard configuration file '{0}' was not found.", file); + } + cmd.AppendSwitchIfNotNull ("--pg-map-output ", ProguardMappingOutput); + } else { + cmd.AppendSwitch ("--no-tree-shaking"); + } + + if (!EnableMinify) + cmd.AppendSwitch ("--no-minification"); + + return cmd; + } + } + +} diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.OSS.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.OSS.cs index 6bce79f2d34..42c60d15e57 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.OSS.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.OSS.cs @@ -60,21 +60,49 @@ public partial class BuildTest : BaseTest /* isRelease */ true, /* enableProguard */ true, /* useLatestSdk */ true, + /* useD8 */ false, }, new Object [] { /* isRelease */ true, /* enableProguard */ false, /* useLatestSdk */ true, + /* useD8 */ false, }, new Object [] { /* isRelease */ false, /* enableProguard */ true, /* useLatestSdk */ true, + /* useD8 */ false, }, new Object [] { /* isRelease */ false, /* enableProguard */ false, /* useLatestSdk */ true, + /* useD8 */ false, + }, + new Object [] { + /* isRelease */ true, + /* enableProguard */ true, + /* useLatestSdk */ true, + /* useD8 */ true, + }, + new Object [] { + /* isRelease */ true, + /* enableProguard */ false, + /* useLatestSdk */ true, + /* useD8 */ true, + }, + new Object [] { + /* isRelease */ false, + /* enableProguard */ true, + /* useLatestSdk */ true, + /* useD8 */ true, + }, + new Object [] { + /* isRelease */ false, + /* enableProguard */ false, + /* useLatestSdk */ true, + /* useD8 */ true, }, }; static object [] TakeSimpleFlag = new object [] { diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs index b50aaf48064..403a1978334 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs @@ -662,17 +662,27 @@ public void BuildAotApplicationAndBundle (string supportedAbis, bool enableLLVM, [Test] [TestCaseSource ("ProguardChecks")] - public void BuildProguardEnabledProject (bool isRelease, bool enableProguard, bool useLatestSdk) + public void BuildProguardEnabledProject (bool isRelease, bool enableProguard, bool useLatestSdk, bool useD8) { var proj = new XamarinAndroidApplicationProject () { IsRelease = isRelease, EnableProguard = enableProguard, UseLatestPlatformSdk = useLatestSdk, TargetFrameworkVersion = useLatestSdk ? "v7.1" : "v5.0" }; - using (var b = CreateApkBuilder (Path.Combine ("temp", $"BuildProguard Enabled Project(1){isRelease}{enableProguard}{useLatestSdk}"))) { + if (useD8) { + proj.DexGenerator = "d8"; + } else { + proj.DexGenerator = "dx"; + } + using (var b = CreateApkBuilder (Path.Combine ("temp", $"BuildProguard Enabled Project(1){isRelease}{enableProguard}{useLatestSdk}{useD8}"))) { Assert.IsTrue (b.Build (proj), "Build should have succeeded."); if (isRelease && enableProguard) { var proguardProjectPrimary = Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath, "proguard", "proguard_project_primary.cfg"); FileAssert.Exists (proguardProjectPrimary); - StringAssertEx.ContainsText (File.ReadAllLines (proguardProjectPrimary), "-keep class md52d9cf6333b8e95e8683a477bc589eda5.MainActivity"); + Assert.IsTrue (StringAssertEx.ContainsText (File.ReadAllLines (proguardProjectPrimary), "-keep class md52d9cf6333b8e95e8683a477bc589eda5.MainActivity"), "`md52d9cf6333b8e95e8683a477bc589eda5.MainActivity` should exist in `proguard_project_primary.cfg`!"); } + + var className = "Lmono/MonoRuntimeProvider;"; + var dexFile = b.Output.GetIntermediaryPath (Path.Combine ("android", "bin", "classes.dex")); + FileAssert.Exists (dexFile); + Assert.IsTrue (DexUtils.ContainsClass (className, dexFile, b.AndroidSdkDirectory), $"`{dexFile}` should include `{className}`!"); } } @@ -2155,13 +2165,18 @@ public void BuildReleaseApplication () } [Test] - public void BuildApplicationWithSpacesInPath ([Values (true, false)] bool isRelease, [Values (true, false)] bool enableProguard, [Values (true, false)] bool enableMultiDex) + public void BuildApplicationWithSpacesInPath ([Values (true, false)] bool isRelease, [Values (true, false)] bool enableProguard, [Values (true, false)] bool enableMultiDex, [Values (true, false)] bool useD8) { var proj = new XamarinAndroidApplicationProject () { IsRelease = isRelease, AotAssemblies = isRelease, EnableProguard = enableProguard, }; + if (useD8) { + proj.DexGenerator = "d8"; + } else { + proj.DexGenerator = "dx"; + } proj.OtherBuildItems.Add (new BuildItem ("AndroidJavaLibrary", "Hello (World).jar") { BinaryContent = () => Convert.FromBase64String (@" UEsDBBQACAgIAMl8lUsAAAAAAAAAAAAAAAAJAAQATUVUQS1JTkYv/soAAAMAUEsHCAAAAAACAAAAA AAAAFBLAwQUAAgICADJfJVLAAAAAAAAAAAAAAAAFAAAAE1FVEEtSU5GL01BTklGRVNULk1G803My0 @@ -2192,9 +2207,14 @@ public void BuildApplicationWithSpacesInPath ([Values (true, false)] bool isRele ", }); } - using (var b = CreateApkBuilder (Path.Combine ("temp", $"BuildReleaseAppWithA InIt({isRelease}{enableProguard}{enableMultiDex})"))) { + using (var b = CreateApkBuilder (Path.Combine ("temp", $"BuildReleaseAppWithA InIt({isRelease}{enableProguard}{enableMultiDex}{useD8})"))) { Assert.IsTrue (b.Build (proj), "Build should have succeeded."); Assert.IsFalse (b.LastBuildOutput.ContainsText ("Duplicate zip entry"), "Should not get warning about [META-INF/MANIFEST.MF]"); + + var className = "Lmono/MonoRuntimeProvider;"; + var dexFile = b.Output.GetIntermediaryPath (Path.Combine ("android", "bin", "classes.dex")); + FileAssert.Exists (dexFile); + Assert.IsTrue (DexUtils.ContainsClass (className, dexFile, b.AndroidSdkDirectory), $"`{dexFile}` should include `{className}`!"); } } @@ -3140,6 +3160,7 @@ public void foo() if (enableDesugar) { var className = "Lmono/MonoRuntimeProvider;"; var dexFile = builder.Output.GetIntermediaryPath (Path.Combine ("android", "bin", "classes.dex")); + FileAssert.Exists (dexFile); Assert.IsTrue (DexUtils.ContainsClass (className, dexFile, builder.AndroidSdkDirectory), $"`{dexFile}` should include `{className}`!"); } } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/KnownProperties.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/KnownProperties.cs index c0d0ca37714..e69ae757b32 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/KnownProperties.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/KnownProperties.cs @@ -17,6 +17,8 @@ public static class KnownProperties public const string BundleAssemblies = "BundleAssemblies"; public const string EnableProguard = "EnableProguard"; public const string AndroidEnableDesugar = "AndroidEnableDesugar"; + public const string AndroidDexGenerator = "AndroidDexGenerator"; + public const string AndroidLinkTool = "AndroidLinkTool"; public const string UseJackAndJill = "UseJackAndJill"; public const string AotAssemblies = "AotAssemblies"; diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinAndroidApplicationProject.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinAndroidApplicationProject.cs index a3804b50f52..f63aaf414ce 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinAndroidApplicationProject.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinAndroidApplicationProject.cs @@ -85,6 +85,16 @@ public bool EnableDesugar { set { SetProperty (KnownProperties.AndroidEnableDesugar, value.ToString ()); } } + public string DexGenerator { + get { return GetProperty (KnownProperties.AndroidDexGenerator); } + set { SetProperty (KnownProperties.AndroidDexGenerator, value); } + } + + public string LinkTool { + get { return GetProperty (KnownProperties.AndroidLinkTool); } + set { SetProperty (KnownProperties.AndroidLinkTool, value); } + } + public string AndroidFastDeploymentType { get { return GetProperty (KnownProperties.AndroidFastDeploymentType); } set { SetProperty (KnownProperties.AndroidFastDeploymentType, value); } diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj index 989eed5a633..fede85c5b62 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj @@ -569,6 +569,8 @@ + + @@ -732,6 +734,11 @@ proguard False + + {1bafa0cc-0377-46ce-ab7b-7bb2e7b62f63} + r8 + False + {E248B2CA-303B-4645-ADDC-9D4459D550FD} libZipSharp diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index e2de6b13c22..b31ed4117f8 100755 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -73,6 +73,8 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. + + @@ -279,7 +281,11 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. $(EnableProguard) False - False + dx + + proguard + True + False 1G @@ -942,6 +948,18 @@ because xbuild doesn't support framework reference assemblies. /> + + + + + + + + @@ -2547,8 +2566,7 @@ because xbuild doesn't support framework reference assemblies. ProguardGeneratedApplicationConfiguration="$(IntermediateOutputPath)proguard\proguard_project_primary.cfg" ProguardConfigurationFiles="$(ProguardConfigFiles)" JavaLibrariesToEmbed="@(_JarsToProguard);@(_InstantRunJavaReference)" - ExternalJavaLibraries="@(AndroidExternalJavaLibrary)" - DoNotPackageJavaLibraries="@(_ResolvedDoNotPackageAttributes)" + JavaLibrariesToReference="@(AndroidExternalJavaLibrary)" ProguardJarOutput="$(IntermediateOutputPath)proguard\__proguard_output__.jar" EnableLogging="$(ProguardEnableLogging)" DumpOutput="$(IntermediateOutputPath)proguard\dump.txt" @@ -2610,8 +2628,66 @@ because xbuild doesn't support framework reference assemblies. + + + + + + + + + + + + + + + + + + + DependsOnTargets="_CompileToDalvikWithDx;_CompileToDalvikWithR8"> <_DexFile Include="$(IntermediateOutputPath)android\bin\dex\*.dex" /> diff --git a/src/r8/r8.csproj b/src/r8/r8.csproj new file mode 100644 index 00000000000..4756b644ec8 --- /dev/null +++ b/src/r8/r8.csproj @@ -0,0 +1,18 @@ + + + Debug + 1bafa0cc-0377-46ce-ab7b-7bb2e7b62f63 + Exe + bin\$(Configuration) + v2.0 + + + + + + + + + + + diff --git a/src/r8/r8.props b/src/r8/r8.props new file mode 100644 index 00000000000..6d7600ad463 --- /dev/null +++ b/src/r8/r8.props @@ -0,0 +1,3 @@ + + + diff --git a/src/r8/r8.targets b/src/r8/r8.targets new file mode 100644 index 00000000000..826dedcade9 --- /dev/null +++ b/src/r8/r8.targets @@ -0,0 +1,119 @@ + + + + + + + + + + + _DownloadDepotTools; + _UnzipDepotTools; + _SetDepotToolsEnvironment; + _BootstrapDepotTools; + _BuildR8; + _CopyR8; + + + _SetDepotToolsEnvironment; + _CleanR8; + _CleanDepotTools; + + + + + + + + + <_DepotToolsZip>..\..\bin\Build$(Configuration)\depot_tools.zip + <_PathToDepotTools>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\..\bin\Build$(Configuration)\depot_tools')) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/Xamarin.Forms-Performance-Integration/Droid/Xamarin.Forms.Performance.Integration.Droid.csproj b/tests/Xamarin.Forms-Performance-Integration/Droid/Xamarin.Forms.Performance.Integration.Droid.csproj index a2bbf29f3d7..1ea6f895149 100644 --- a/tests/Xamarin.Forms-Performance-Integration/Droid/Xamarin.Forms.Performance.Integration.Droid.csproj +++ b/tests/Xamarin.Forms-Performance-Integration/Droid/Xamarin.Forms.Performance.Integration.Droid.csproj @@ -17,6 +17,7 @@ Assets false True + d8 @@ -40,6 +41,7 @@ true false armeabi-v7a;x86 + r8