From 9d7a65252e59ed8629120d430dcc004ac5ae5d4c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 19:27:05 +0000 Subject: [PATCH 1/7] Initial plan From 650d2efb3bfea54566afa47a6d1cfd3626c4e638 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 20:08:25 +0000 Subject: [PATCH 2/7] Implement response file support for D8/R8 tasks to avoid command line length limits Co-authored-by: jonathanpeppers <840039+jonathanpeppers@users.noreply.github.com> --- src/Xamarin.Android.Build.Tasks/Tasks/D8.cs | 83 ++++++++++++++++----- 1 file changed, 66 insertions(+), 17 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/D8.cs b/src/Xamarin.Android.Build.Tasks/Tasks/D8.cs index c7c15fcf065..2e1a2c0bfb8 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/D8.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/D8.cs @@ -16,6 +16,8 @@ public class D8 : JavaToolTask { public override string TaskPrefix => "DX8"; + string? responseFilePath; + [Required] public string JarPath { get; set; } = ""; @@ -44,6 +46,17 @@ public class D8 : JavaToolTask public string? ExtraArguments { get; set; } + public override bool RunTask () + { + try { + return base.RunTask (); + } finally { + if (responseFilePath is { Length: > 0 } && File.Exists (responseFilePath)) { + File.Delete (responseFilePath); + } + } + } + protected override string GenerateCommandLineCommands () { return GetCommandLineBuilder ().ToString (); @@ -83,6 +96,38 @@ protected virtual CommandLineBuilder GetCommandLineBuilder () if (!EnableDesugar) cmd.AppendSwitch ("--no-desugaring"); + cmd.AppendSwitchIfNotNull ("--output ", OutputDirectory); + + // Create response file with jar libraries and inputs to avoid command line length limits + responseFilePath = CreateResponseFile (); + cmd.AppendSwitch ($"@{responseFilePath}"); + + if (MapDiagnostics != null) { + foreach (var diagnostic in MapDiagnostics) { + var from = diagnostic.ItemSpec; + var to = diagnostic.GetMetadata ("To"); + if (from is not { Length: > 0 } || to is not { Length: > 0 }) + continue; + cmd.AppendSwitch ("--map-diagnostics"); + cmd.AppendSwitch (from); + cmd.AppendSwitch (to); + } + } + + return cmd; + } + + /// + /// Creates a response file containing --lib and input jar arguments for R8/D8. + /// This avoids command line length limits that can occur with many jar libraries. + /// + protected virtual string CreateResponseFile () + { + var responseFile = Path.GetTempFileName (); + Log.LogDebugMessage ($"[{MainClass}] response file: {responseFile}"); + + using var response = new StreamWriter (responseFile, append: false, encoding: Files.UTF8withoutBOM); + var injars = new List (); var libjars = new List (); if (AlternativeJarLibrariesToEmbed?.Length > 0) { @@ -106,25 +151,29 @@ protected virtual CommandLineBuilder GetCommandLineBuilder () } } - cmd.AppendSwitchIfNotNull ("--output ", OutputDirectory); - foreach (var jar in libjars) - cmd.AppendSwitchIfNotNull ("--lib ", jar); - foreach (var jar in injars) - cmd.AppendFileNameIfNotNull (jar); - - if (MapDiagnostics != null) { - foreach (var diagnostic in MapDiagnostics) { - var from = diagnostic.ItemSpec; - var to = diagnostic.GetMetadata ("To"); - if (from is not { Length: > 0 } || to is not { Length: > 0 }) - continue; - cmd.AppendSwitch ("--map-diagnostics"); - cmd.AppendSwitch (from); - cmd.AppendSwitch (to); - } + foreach (var jar in libjars) { + WriteArg (response, "--lib"); + WriteArg (response, jar); + } + foreach (var jar in injars) { + WriteArg (response, jar); } - return cmd; + return responseFile; + } + + /// + /// Writes a single argument to the response file, quoting if necessary for paths with spaces. + /// + protected void WriteArg (StreamWriter writer, string arg) + { + // Quote paths that contain spaces + if (arg.Contains (" ")) { + writer.WriteLine ($"\"{arg}\""); + } else { + writer.WriteLine (arg); + } + Log.LogDebugMessage ($" {arg}"); } // Note: We do not want to call the base.LogEventsFromTextOutput as it will incorrectly identify From 54d84db39b027beb02b73fbe469c2b43eca48625 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 20:16:25 +0000 Subject: [PATCH 3/7] Add unit tests for D8 response file generation Co-authored-by: jonathanpeppers <840039+jonathanpeppers@users.noreply.github.com> --- .../Tasks/D8Tests.cs | 175 ++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/D8Tests.cs diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/D8Tests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/D8Tests.cs new file mode 100644 index 00000000000..5e02d38fc3a --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/D8Tests.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using NUnit.Framework; +using Xamarin.Android.Tasks; + +namespace Xamarin.Android.Build.Tests +{ + [TestFixture] + public class D8Tests + { + MockBuildEngine engine; + List messages; + string tempDir; + + [SetUp] + public void Setup () + { + engine = new MockBuildEngine (TestContext.Out, + messages: messages = new List ()); + tempDir = Path.Combine (Path.GetTempPath (), "D8Tests_" + Guid.NewGuid ().ToString ("N")); + Directory.CreateDirectory (tempDir); + } + + [TearDown] + public void TearDown () + { + if (Directory.Exists (tempDir)) { + Directory.Delete (tempDir, recursive: true); + } + } + + /// + /// Tests that the D8 task creates a response file with the expected content. + /// This test uses a test subclass to avoid actually running Java. + /// + [Test] + public void ResponseFileContainsLibAndInputJars () + { + // Create mock jar files for testing + var platformJar = Path.Combine (tempDir, "android.jar"); + var inputJar1 = Path.Combine (tempDir, "input1.jar"); + var inputJar2 = Path.Combine (tempDir, "input2.jar"); + var libJar1 = Path.Combine (tempDir, "lib1.jar"); + File.WriteAllText (platformJar, "mock"); + File.WriteAllText (inputJar1, "mock"); + File.WriteAllText (inputJar2, "mock"); + File.WriteAllText (libJar1, "mock"); + + var d8Task = new D8TestTask { + BuildEngine = engine, + JarPath = "d8.jar", + JavaPlatformJarPath = platformJar, + OutputDirectory = tempDir, + JavaLibrariesToEmbed = new ITaskItem [] { + new TaskItem (inputJar1), + new TaskItem (inputJar2), + }, + JavaLibrariesToReference = new ITaskItem [] { + new TaskItem (libJar1), + }, + }; + + string commandLine = d8Task.TestGenerateCommandLineCommands (); + string responseFilePath = d8Task.ResponseFilePath; + + try { + // Verify response file was created + Assert.IsNotNull (responseFilePath, "Response file path should not be null"); + FileAssert.Exists (responseFilePath, "Response file should exist"); + + // Verify the response file is referenced in the command line + Assert.IsTrue (commandLine.Contains ($"@{responseFilePath}"), "Command line should reference the response file"); + + // Read and verify response file content + string [] responseFileContent = File.ReadAllLines (responseFilePath); + + // Should contain --lib entries for platform jar and reference jars + Assert.IsTrue (responseFileContent.Any (line => line == "--lib"), "Response file should contain --lib switch"); + Assert.IsTrue (responseFileContent.Any (line => line.Contains ("android.jar")), "Response file should contain android.jar"); + Assert.IsTrue (responseFileContent.Any (line => line.Contains ("lib1.jar")), "Response file should contain lib1.jar"); + + // Should contain input jars as direct arguments (no --lib prefix) + Assert.IsTrue (responseFileContent.Any (line => line.Contains ("input1.jar")), "Response file should contain input1.jar"); + Assert.IsTrue (responseFileContent.Any (line => line.Contains ("input2.jar")), "Response file should contain input2.jar"); + + } finally { + // Clean up response file + if (responseFilePath != null && File.Exists (responseFilePath)) { + File.Delete (responseFilePath); + } + } + } + + /// + /// Tests that paths with spaces are quoted in the response file. + /// + [Test] + public void ResponseFileQuotesPathsWithSpaces () + { + var pathWithSpaces = Path.Combine (tempDir, "path with spaces"); + Directory.CreateDirectory (pathWithSpaces); + + // Create mock jar files for testing + var platformJar = Path.Combine (pathWithSpaces, "android.jar"); + var inputJar = Path.Combine (pathWithSpaces, "input.jar"); + File.WriteAllText (platformJar, "mock"); + File.WriteAllText (inputJar, "mock"); + + var d8Task = new D8TestTask { + BuildEngine = engine, + JarPath = "d8.jar", + JavaPlatformJarPath = platformJar, + OutputDirectory = tempDir, + JavaLibrariesToEmbed = new ITaskItem [] { + new TaskItem (inputJar), + }, + }; + + d8Task.TestGenerateCommandLineCommands (); + string responseFilePath = d8Task.ResponseFilePath; + + try { + FileAssert.Exists (responseFilePath, "Response file should exist"); + string responseFileContent = File.ReadAllText (responseFilePath); + + // Paths with spaces should be quoted + Assert.IsTrue (responseFileContent.Contains ("\""), "Response file should contain quoted paths"); + Assert.IsTrue (responseFileContent.Contains ("path with spaces"), "Response file should contain the path with spaces"); + + } finally { + if (responseFilePath != null && File.Exists (responseFilePath)) { + File.Delete (responseFilePath); + } + } + } + } + + /// + /// Test subclass of D8 that exposes internal methods for testing without needing Java. + /// + internal class D8TestTask : D8 + { + /// + /// The path to the response file created by the last call to TestGenerateCommandLineCommands. + /// + public string ResponseFilePath { get; private set; } + + /// + /// Test method that generates command line without actually running the task. + /// + public string TestGenerateCommandLineCommands () + { + var cmd = GetCommandLineBuilder (); + // Capture the response file path after command line generation + ResponseFilePath = GetResponseFilePathFromCommandLine (cmd.ToString ()); + return cmd.ToString (); + } + + private static string GetResponseFilePathFromCommandLine (string commandLine) + { + // Find the @filepath argument + var startIndex = commandLine.IndexOf ("@", StringComparison.Ordinal); + if (startIndex < 0) return null; + + var endIndex = commandLine.IndexOf (" ", startIndex, StringComparison.Ordinal); + if (endIndex < 0) endIndex = commandLine.Length; + + return commandLine.Substring (startIndex + 1, endIndex - startIndex - 1); + } + } +} From ec829454558fa09bcd29618be97072cac0938a19 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 20:26:04 +0000 Subject: [PATCH 4/7] Address code review feedback - add bounds checking in test Co-authored-by: jonathanpeppers <840039+jonathanpeppers@users.noreply.github.com> --- .../Tests/Xamarin.Android.Build.Tests/Tasks/D8Tests.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/D8Tests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/D8Tests.cs index 5e02d38fc3a..22168955ec1 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/D8Tests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/D8Tests.cs @@ -78,7 +78,7 @@ public void ResponseFileContainsLibAndInputJars () // Read and verify response file content string [] responseFileContent = File.ReadAllLines (responseFilePath); - // Should contain --lib entries for platform jar and reference jars + // Should contain --lib entries (written as separate lines: --lib on one line, path on next) Assert.IsTrue (responseFileContent.Any (line => line == "--lib"), "Response file should contain --lib switch"); Assert.IsTrue (responseFileContent.Any (line => line.Contains ("android.jar")), "Response file should contain android.jar"); Assert.IsTrue (responseFileContent.Any (line => line.Contains ("lib1.jar")), "Response file should contain lib1.jar"); @@ -166,6 +166,9 @@ private static string GetResponseFilePathFromCommandLine (string commandLine) var startIndex = commandLine.IndexOf ("@", StringComparison.Ordinal); if (startIndex < 0) return null; + // Check if '@' is the last character + if (startIndex + 1 >= commandLine.Length) return null; + var endIndex = commandLine.IndexOf (" ", startIndex, StringComparison.Ordinal); if (endIndex < 0) endIndex = commandLine.Length; From c387499feb97bcd4b28811ae87fa8235a0bd58df Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 17:27:36 +0000 Subject: [PATCH 5/7] Fix response file: Remove quoting as R8/D8 treats each line as complete argument Co-authored-by: jonathanpeppers <840039+jonathanpeppers@users.noreply.github.com> --- src/Xamarin.Android.Build.Tasks/Tasks/D8.cs | 10 +++------- .../Tests/Xamarin.Android.Build.Tests/Tasks/D8Tests.cs | 9 +++++---- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/D8.cs b/src/Xamarin.Android.Build.Tasks/Tasks/D8.cs index 2e1a2c0bfb8..2ae394ed345 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/D8.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/D8.cs @@ -163,16 +163,12 @@ protected virtual string CreateResponseFile () } /// - /// Writes a single argument to the response file, quoting if necessary for paths with spaces. + /// Writes a single argument to the response file. + /// R8/D8 response files treat each line as a complete argument, so no quoting is needed. /// protected void WriteArg (StreamWriter writer, string arg) { - // Quote paths that contain spaces - if (arg.Contains (" ")) { - writer.WriteLine ($"\"{arg}\""); - } else { - writer.WriteLine (arg); - } + writer.WriteLine (arg); Log.LogDebugMessage ($" {arg}"); } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/D8Tests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/D8Tests.cs index 22168955ec1..f86c0b4f9d4 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/D8Tests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/D8Tests.cs @@ -96,10 +96,11 @@ public void ResponseFileContainsLibAndInputJars () } /// - /// Tests that paths with spaces are quoted in the response file. + /// Tests that paths with spaces are written correctly to the response file. + /// R8/D8 response files treat each line as a complete argument, so no quoting is needed. /// [Test] - public void ResponseFileQuotesPathsWithSpaces () + public void ResponseFileHandlesPathsWithSpaces () { var pathWithSpaces = Path.Combine (tempDir, "path with spaces"); Directory.CreateDirectory (pathWithSpaces); @@ -127,8 +128,8 @@ public void ResponseFileQuotesPathsWithSpaces () FileAssert.Exists (responseFilePath, "Response file should exist"); string responseFileContent = File.ReadAllText (responseFilePath); - // Paths with spaces should be quoted - Assert.IsTrue (responseFileContent.Contains ("\""), "Response file should contain quoted paths"); + // Paths with spaces should NOT be quoted (R8/D8 treats each line as a complete argument) + Assert.IsFalse (responseFileContent.Contains ("\""), "Response file should not contain quoted paths"); Assert.IsTrue (responseFileContent.Contains ("path with spaces"), "Response file should contain the path with spaces"); } finally { From 75f42353d583507fddb41e4c35c0979d77df3fe5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 28 Jan 2026 20:47:20 +0000 Subject: [PATCH 6/7] Use IsNullOrEmpty() extension method in D8.cs and add R8-specific args to response file Co-authored-by: jonathanpeppers <840039+jonathanpeppers@users.noreply.github.com> --- src/Xamarin.Android.Build.Tasks/Tasks/D8.cs | 12 +++---- src/Xamarin.Android.Build.Tasks/Tasks/R8.cs | 38 ++++++++++++++------- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/D8.cs b/src/Xamarin.Android.Build.Tasks/Tasks/D8.cs index 2ae394ed345..c579c27a595 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/D8.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/D8.cs @@ -51,7 +51,7 @@ public override bool RunTask () try { return base.RunTask (); } finally { - if (responseFilePath is { Length: > 0 } && File.Exists (responseFilePath)) { + if (!responseFilePath.IsNullOrEmpty () && File.Exists (responseFilePath)) { File.Delete (responseFilePath); } } @@ -70,14 +70,14 @@ protected virtual CommandLineBuilder GetCommandLineBuilder () { var cmd = new CommandLineBuilder (); - if (JavaOptions is { Length: > 0 }) { + if (!JavaOptions.IsNullOrEmpty ()) { cmd.AppendSwitch (JavaOptions); } cmd.AppendSwitchIfNotNull ("-Xmx", JavaMaximumHeapSize); cmd.AppendSwitchIfNotNull ("-classpath ", JarPath); cmd.AppendSwitch (MainClass); - if (ExtraArguments is { Length: > 0 }) + if (!ExtraArguments.IsNullOrEmpty ()) cmd.AppendSwitch (ExtraArguments); // it should contain "--dex". if (Debug) cmd.AppendSwitch ("--debug"); @@ -85,7 +85,7 @@ protected virtual CommandLineBuilder GetCommandLineBuilder () cmd.AppendSwitch ("--release"); //NOTE: if this is blank, we can omit --min-api in this call - if (AndroidManifestFile is { Length: > 0 }) { + if (!AndroidManifestFile.IsNullOrEmpty ()) { var doc = AndroidAppManifest.Load (AndroidManifestFile, MonoAndroidHelper.SupportedVersions); if (doc.MinSdkVersion.HasValue) { MinSdkVersion = doc.MinSdkVersion.Value; @@ -106,7 +106,7 @@ protected virtual CommandLineBuilder GetCommandLineBuilder () foreach (var diagnostic in MapDiagnostics) { var from = diagnostic.ItemSpec; var to = diagnostic.GetMetadata ("To"); - if (from is not { Length: > 0 } || to is not { Length: > 0 }) + if (from.IsNullOrEmpty () || to.IsNullOrEmpty ()) continue; cmd.AppendSwitch ("--map-diagnostics"); cmd.AppendSwitch (from); @@ -137,7 +137,7 @@ protected virtual string CreateResponseFile () } } else if (JavaLibrariesToEmbed != null) { Log.LogDebugMessage (" processing ClassesZip, JavaLibrariesToEmbed..."); - if (ClassesZip is { Length: > 0 } && File.Exists (ClassesZip)) { + if (!ClassesZip.IsNullOrEmpty () && File.Exists (ClassesZip)) { injars.Add (ClassesZip); } foreach (var jar in JavaLibrariesToEmbed) { diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/R8.cs b/src/Xamarin.Android.Build.Tasks/Tasks/R8.cs index f2dee391d4b..fbf66e7bf48 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/R8.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/R8.cs @@ -51,9 +51,17 @@ public override bool RunTask () } } - protected override CommandLineBuilder GetCommandLineBuilder () + /// + /// Override CreateResponseFile to add R8-specific arguments to the response file. + /// This ensures all arguments are passed via response file to avoid command line length limits. + /// + protected override string CreateResponseFile () { - var cmd = base.GetCommandLineBuilder (); + // First, get the base response file path and write base D8 arguments + var responseFile = base.CreateResponseFile (); + + // Now append R8-specific arguments to the response file + using var response = new StreamWriter (responseFile, append: true, encoding: Files.UTF8withoutBOM); if (EnableMultiDex) { if (MinSdkVersion >= 21) { @@ -77,9 +85,12 @@ protected override CommandLineBuilder GetCommandLineBuilder () } File.WriteAllText (temp, string.Concat (content)); - cmd.AppendSwitchIfNotNull ("--main-dex-list ", temp); - cmd.AppendSwitchIfNotNull ("--main-dex-rules ", Path.Combine (AndroidSdkBuildToolsPath, "mainDexClasses.rules")); - cmd.AppendSwitchIfNotNull ("--main-dex-list-output ", MultiDexMainDexListFile); + WriteArg (response, "--main-dex-list"); + WriteArg (response, temp); + WriteArg (response, "--main-dex-rules"); + WriteArg (response, Path.Combine (AndroidSdkBuildToolsPath, "mainDexClasses.rules")); + WriteArg (response, "--main-dex-list-output"); + WriteArg (response, MultiDexMainDexListFile); } } @@ -112,8 +123,8 @@ protected override CommandLineBuilder GetCommandLineBuilder () } } else { //NOTE: we may be calling r8 *only* for multi-dex, and all shrinking is disabled - cmd.AppendSwitch ("--no-tree-shaking"); - cmd.AppendSwitch ("--no-minification"); + WriteArg (response, "--no-tree-shaking"); + WriteArg (response, "--no-minification"); // Rules to turn off optimizations var temp = Path.GetTempFileName (); var lines = new List { @@ -131,18 +142,21 @@ protected override CommandLineBuilder GetCommandLineBuilder () } File.WriteAllLines (temp, lines); tempFiles.Add (temp); - cmd.AppendSwitchIfNotNull ("--pg-conf ", temp); + WriteArg (response, "--pg-conf"); + WriteArg (response, temp); } if (ProguardConfigurationFiles != null) { foreach (var file in ProguardConfigurationFiles) { - if (File.Exists (file)) - cmd.AppendSwitchIfNotNull ("--pg-conf ", file); - else + if (File.Exists (file)) { + WriteArg (response, "--pg-conf"); + WriteArg (response, file); + } else { Log.LogCodedWarning ("XA4304", file, 0, Properties.Resources.XA4304, file); + } } } - return cmd; + return responseFile; } // Note: We do not want to call the base.LogEventsFromTextOutput as it will incorrectly identify From 81fc2e069790bac1ef3ac818f838c54110c996c9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 28 Jan 2026 21:34:27 +0000 Subject: [PATCH 7/7] Move all D8/R8 switches to response file, keep only JVM args on command line Co-authored-by: jonathanpeppers <840039+jonathanpeppers@users.noreply.github.com> --- src/Xamarin.Android.Build.Tasks/Tasks/D8.cs | 62 ++++++++++++--------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/D8.cs b/src/Xamarin.Android.Build.Tasks/Tasks/D8.cs index c579c27a595..797d63e657c 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/D8.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/D8.cs @@ -70,6 +70,7 @@ protected virtual CommandLineBuilder GetCommandLineBuilder () { var cmd = new CommandLineBuilder (); + // Only JVM arguments go on the command line, everything else goes in the response file if (!JavaOptions.IsNullOrEmpty ()) { cmd.AppendSwitch (JavaOptions); } @@ -77,57 +78,64 @@ protected virtual CommandLineBuilder GetCommandLineBuilder () cmd.AppendSwitchIfNotNull ("-classpath ", JarPath); cmd.AppendSwitch (MainClass); + // Create response file with all D8/R8 arguments to avoid command line length limits + responseFilePath = CreateResponseFile (); + cmd.AppendSwitch ($"@{responseFilePath}"); + + return cmd; + } + + /// + /// Creates a response file containing all D8/R8 arguments. + /// This avoids command line length limits that can occur with many jar libraries. + /// + protected virtual string CreateResponseFile () + { + var responseFile = Path.GetTempFileName (); + Log.LogDebugMessage ($"[{MainClass}] response file: {responseFile}"); + + using var response = new StreamWriter (responseFile, append: false, encoding: Files.UTF8withoutBOM); + + // D8/R8 switches if (!ExtraArguments.IsNullOrEmpty ()) - cmd.AppendSwitch (ExtraArguments); // it should contain "--dex". + WriteArg (response, ExtraArguments); // it should contain "--dex". if (Debug) - cmd.AppendSwitch ("--debug"); + WriteArg (response, "--debug"); else - cmd.AppendSwitch ("--release"); + WriteArg (response, "--release"); //NOTE: if this is blank, we can omit --min-api in this call if (!AndroidManifestFile.IsNullOrEmpty ()) { var doc = AndroidAppManifest.Load (AndroidManifestFile, MonoAndroidHelper.SupportedVersions); if (doc.MinSdkVersion.HasValue) { MinSdkVersion = doc.MinSdkVersion.Value; - cmd.AppendSwitchIfNotNull ("--min-api ", MinSdkVersion.ToString ()); + WriteArg (response, "--min-api"); + WriteArg (response, MinSdkVersion.ToString ()); } } if (!EnableDesugar) - cmd.AppendSwitch ("--no-desugaring"); - - cmd.AppendSwitchIfNotNull ("--output ", OutputDirectory); + WriteArg (response, "--no-desugaring"); - // Create response file with jar libraries and inputs to avoid command line length limits - responseFilePath = CreateResponseFile (); - cmd.AppendSwitch ($"@{responseFilePath}"); + if (!OutputDirectory.IsNullOrEmpty ()) { + WriteArg (response, "--output"); + WriteArg (response, OutputDirectory); + } + // --map-diagnostics if (MapDiagnostics != null) { foreach (var diagnostic in MapDiagnostics) { var from = diagnostic.ItemSpec; var to = diagnostic.GetMetadata ("To"); if (from.IsNullOrEmpty () || to.IsNullOrEmpty ()) continue; - cmd.AppendSwitch ("--map-diagnostics"); - cmd.AppendSwitch (from); - cmd.AppendSwitch (to); + WriteArg (response, "--map-diagnostics"); + WriteArg (response, from); + WriteArg (response, to); } } - return cmd; - } - - /// - /// Creates a response file containing --lib and input jar arguments for R8/D8. - /// This avoids command line length limits that can occur with many jar libraries. - /// - protected virtual string CreateResponseFile () - { - var responseFile = Path.GetTempFileName (); - Log.LogDebugMessage ($"[{MainClass}] response file: {responseFile}"); - - using var response = new StreamWriter (responseFile, append: false, encoding: Files.UTF8withoutBOM); - + // --lib and input jars var injars = new List (); var libjars = new List (); if (AlternativeJarLibrariesToEmbed?.Length > 0) {