From 00242e63f6e58c53c9076d381cffa42b0bfb964d Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 4 Jan 2018 03:37:42 -0600 Subject: [PATCH] [Xamarin.Android.Build.Tasks] fix classes-zip inception (#1142) Fixes: https://bugzilla.xamarin.com/show_bug.cgi?id=61073 Context: a296784 When we added the new `classes.zip` functionality, there was one oddity present. On the first build, everything appears to be normal. On the second build, `classes.zip` appeared to contain itself inside itself??? To make things even stranger, I wrote a unit test that contains a `Java.Lang.Object` subclass in which its type and namespace names change on each write. This worked fine on macOS, even with the oddity of `classes.zip` continually zipping itself inside itself... The new java sources would appear at the bottom of the list within the zip file. Everything broke down when I tried my unit test on Windows, the case of `classes.zip` trying to compress itself inside itself was silently failing. It was not processing any further files after the failing zip entry, and so *in theory* this situation would cause many Java stubs to be missing from the zip. Solution? Let's just put `classes.zip` up one directory. So instead of: ``` obj/Debug/android/bin/classes/classes.zip ``` Move it to: ``` obj/Debug/android/bin/classes.zip ``` This prevents the `classes` directory from containing `classes.zip`, and our strange case of `classes.zip` inception. Merry Christmas! I'm going to go play Skylanders now. --- .../Tasks/CompileToDalvik.cs | 2 +- .../Tasks/CreateMultiDexMainDexClassList.cs | 4 +- .../Tasks/Javac.cs | 2 +- .../Tasks/Proguard.cs | 2 +- .../Xamarin.Android.Build.Tests/BuildTest.cs | 48 +++++++++++++++++++ 5 files changed, 53 insertions(+), 5 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CompileToDalvik.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CompileToDalvik.cs index 175d51daa2b..ba9d6e41401 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/CompileToDalvik.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/CompileToDalvik.cs @@ -120,7 +120,7 @@ protected override string GenerateCommandLineCommands () if (File.Exists (OptionalObfuscatedJarFile)) cmd.AppendFileNameIfNotNull (OptionalObfuscatedJarFile); else { - var zip = Path.GetFullPath (Path.Combine (ClassesOutputDirectory, "classes.zip")); + var zip = Path.GetFullPath (Path.Combine (ClassesOutputDirectory, "..", "classes.zip")); if (!File.Exists (zip)) { throw new FileNotFoundException ($"'{zip}' does not exist. Please rebuild the project."); } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CreateMultiDexMainDexClassList.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CreateMultiDexMainDexClassList.cs index 4df9cd1de23..e01d09c07de 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/CreateMultiDexMainDexClassList.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/CreateMultiDexMainDexClassList.cs @@ -74,7 +74,7 @@ protected override string GenerateCommandLineCommands () void GenerateProguardCommands (CommandLineBuilder cmd) { var enclosingChar = OS.IsWindows ? "\"" : string.Empty; - var jars = JavaLibraries.Select (i => i.ItemSpec).Concat (new string [] { Path.Combine (ClassesOutputDirectory, "classes.zip") }); + var jars = JavaLibraries.Select (i => i.ItemSpec).Concat (new string [] { Path.Combine (ClassesOutputDirectory, "..", "classes.zip") }); cmd.AppendSwitchIfNotNull ("-jar ", ProguardJarPath); cmd.AppendSwitchUnquotedIfNotNull ("-injars ", $"{enclosingChar}'" + string.Join ($"'{Path.PathSeparator}'", jars) + $"'{enclosingChar}"); cmd.AppendSwitch ("-dontwarn"); @@ -91,7 +91,7 @@ void GenerateMainDexListBuilderCommands(CommandLineBuilder cmd) { var enclosingDoubleQuote = OS.IsWindows ? "\"" : string.Empty; var enclosingQuote = OS.IsWindows ? string.Empty : "'"; - var jars = JavaLibraries.Select (i => i.ItemSpec).Concat (new string [] { Path.Combine (ClassesOutputDirectory, "classes.zip") }); + var jars = JavaLibraries.Select (i => i.ItemSpec).Concat (new string [] { Path.Combine (ClassesOutputDirectory, "..", "classes.zip") }); cmd.AppendSwitchIfNotNull ("-Djava.ext.dirs=", Path.Combine (AndroidSdkBuildToolsPath, "lib")); cmd.AppendSwitch ("com.android.multidex.MainDexListBuilder"); cmd.AppendSwitch ($"{enclosingDoubleQuote}{tempJar}{enclosingDoubleQuote}"); diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/Javac.cs b/src/Xamarin.Android.Build.Tasks/Tasks/Javac.cs index b5dd7c724cc..02982960874 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/Javac.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/Javac.cs @@ -35,7 +35,7 @@ public override bool Execute () if (!result) return result; // compress all the class files - using (var zip = new ZipArchiveEx (Path.Combine (ClassesOutputDirectory, "classes.zip"), FileMode.OpenOrCreate)) + using (var zip = new ZipArchiveEx (Path.Combine (ClassesOutputDirectory, "..", "classes.zip"), FileMode.OpenOrCreate)) zip.AddDirectory (ClassesOutputDirectory, ""); return result; } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/Proguard.cs b/src/Xamarin.Android.Build.Tasks/Tasks/Proguard.cs index 6e26cae5559..93304b4d400 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/Proguard.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/Proguard.cs @@ -125,7 +125,7 @@ protected override string GenerateCommandLineCommands () if (!ClassesOutputDirectory.EndsWith (Path.DirectorySeparatorChar.ToString (), StringComparison.OrdinalIgnoreCase)) ClassesOutputDirectory += Path.DirectorySeparatorChar; - var classesZip = Path.Combine (ClassesOutputDirectory, "classes.zip"); + var classesZip = Path.Combine (ClassesOutputDirectory, "..", "classes.zip"); var acwLines = File.ReadAllLines (AcwMapFile); using (var appcfg = File.CreateText (ProguardGeneratedApplicationConfiguration)) for (int i = 0; i + 3 < acwLines.Length; i += 4) 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 b31c90dbb20..7a3255c700e 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 @@ -142,6 +142,54 @@ public void BuildApplicationWithLibraryAndClean ([Values (false, true)] bool isR } } + [Test] + public void BuildIncrementingClassName () + { + int count = 0; + var source = new BuildItem ("Compile", "World.cs") { + TextContent = () => { + int current = ++count; + return $"namespace Hello{current} {{ public class World{current} : Java.Lang.Object {{ }} }}"; + } + }; + var proj = new XamarinAndroidApplicationProject (); + proj.Sources.Add (source); + + using (var b = CreateApkBuilder ("temp/BuildIncrementingClassName")) { + Assert.IsTrue (b.Build (proj), "Build should have succeeded."); + + var classesZipPath = Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath, "android", "bin", "classes.zip"); + FileAssert.Exists (classesZipPath); + var expectedBuilder = new StringBuilder (); + using (var zip = ZipHelper.OpenZip (classesZipPath)) { + foreach (var file in zip) { + expectedBuilder.AppendLine (file.FullName); + } + } + var expectedZip = expectedBuilder.ToString (); + + source.Timestamp = null; //Force the file to re-save w/ new Timestamp + Assert.IsTrue (b.Build (proj), "Second build should have succeeded."); + + var actualBuilder = new StringBuilder (); + using (var zip = ZipHelper.OpenZip (classesZipPath)) { + foreach (var file in zip) { + actualBuilder.AppendLine (file.FullName); + } + } + var actualZip = actualBuilder.ToString (); + Assert.AreNotEqual (expectedZip, actualZip); + + //Build with no changes + Assert.IsTrue (b.Build (proj), "Third build should have succeeded."); + FileAssert.Exists (classesZipPath); + + //Clean + Assert.IsTrue (b.Clean (proj), "Clean should have succeeded."); + FileAssert.DoesNotExist (classesZipPath); + } + } + [Test] public void BuildMkBundleApplicationRelease () {