From d2983e9811c9d48d34be80fb30af04fd79111782 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Wed, 15 Mar 2017 16:49:22 +0000 Subject: [PATCH] [Xamarin.Android.Build.Tasks] Hitting "System.IO.IOException: Too many open files" when building a large app with AOT and all supported architectures Fixes https://bugzilla.xamarin.com/show_bug.cgi?id=53122 There is a problem with libzip in that is does not have an API for Flushing the current items in memory to disk. This was why ZipArchiveEx was introduced in commit 9166e036. This commit reworks the BuildApk task to make sure of this new functinality and to periodically flush the zip to disk. This will reduce the chances of running out of memory. It also adds a unit test which generates and builds an app which has a large number of refernces and assets. This app also gets AOT'd so it should be pushing the system to the limit. Also added code to make sure the cross tools and llc have execute permissions. Added code to make sure we dispose of each process in the Aapt and Aot tasks. This is to ensure any files or pipes these processed have are removed as soon as possible. Updated the unix-distribution-setup.targets to ensure that the cross-arm tooling and llc have the correct execute permissions on MacOS and Linux. --- .../unix-distribution-setup.targets | 8 ++ src/Xamarin.Android.Build.Tasks/Tasks/Aapt.cs | 61 +++++------ src/Xamarin.Android.Build.Tasks/Tasks/Aot.cs | 38 +++++-- .../Tasks/BuildApk.cs | 102 ++++++++++++------ .../Xamarin.Android.Build.Tests/BuildTest.cs | 96 +++++++++++++++++ .../IncrementalBuildTest.cs | 4 +- .../Common/SolutionBuilder.cs | 28 +++-- .../Utilities/ZipArchiveEx.cs | 20 +++- 8 files changed, 270 insertions(+), 87 deletions(-) diff --git a/build-tools/unix-distribution-setup/unix-distribution-setup.targets b/build-tools/unix-distribution-setup/unix-distribution-setup.targets index edca94d5465..d7535d1c5d1 100644 --- a/build-tools/unix-distribution-setup/unix-distribution-setup.targets +++ b/build-tools/unix-distribution-setup/unix-distribution-setup.targets @@ -9,6 +9,14 @@ Condition=" '$(HostOS)' != 'Windows' " Command="chmod +x $(OutputPath)bin\generator" /> + + diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/Aapt.cs b/src/Xamarin.Android.Build.Tasks/Tasks/Aapt.cs index 6e08e31f75a..de9ff551bd8 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/Aapt.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/Aapt.cs @@ -99,36 +99,37 @@ bool RunAapt (string commandLine) WindowStyle = ProcessWindowStyle.Hidden, }; - var proc = new Process (); - proc.OutputDataReceived += (sender, e) => { - if (e.Data != null) - LogEventsFromTextOutput (e.Data, MessageImportance.Normal); - else - stdout_completed.Set (); - }; - proc.ErrorDataReceived += (sender, e) => { - if (e.Data != null) - LogEventsFromTextOutput (e.Data, MessageImportance.Normal); - else - stderr_completed.Set (); - }; - proc.StartInfo = psi; - proc.Start (); - proc.BeginOutputReadLine (); - proc.BeginErrorReadLine (); - Token.Register (() => { - try { - proc.Kill (); - } catch (Exception) { - } - }); - LogDebugMessage ("Executing {0}", commandLine); - proc.WaitForExit (); - if (psi.RedirectStandardError) - stderr_completed.WaitOne (TimeSpan.FromSeconds (30)); - if (psi.RedirectStandardOutput) - stdout_completed.WaitOne (TimeSpan.FromSeconds (30)); - return proc.ExitCode == 0; + using (var proc = new Process ()) { + proc.OutputDataReceived += (sender, e) => { + if (e.Data != null) + LogEventsFromTextOutput (e.Data, MessageImportance.Normal); + else + stdout_completed.Set (); + }; + proc.ErrorDataReceived += (sender, e) => { + if (e.Data != null) + LogEventsFromTextOutput (e.Data, MessageImportance.Normal); + else + stderr_completed.Set (); + }; + proc.StartInfo = psi; + proc.Start (); + proc.BeginOutputReadLine (); + proc.BeginErrorReadLine (); + Token.Register (() => { + try { + proc.Kill (); + } catch (Exception) { + } + }); + LogDebugMessage ("Executing {0}", commandLine); + proc.WaitForExit (); + if (psi.RedirectStandardError) + stderr_completed.WaitOne (TimeSpan.FromSeconds (30)); + if (psi.RedirectStandardOutput) + stdout_completed.WaitOne (TimeSpan.FromSeconds (30)); + return proc.ExitCode == 0; + } } bool ExecuteForAbi (string cmd, string currentResourceOutputFile) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/Aot.cs b/src/Xamarin.Android.Build.Tasks/Tasks/Aot.cs index 6411dec6b70..4f00d3507cf 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/Aot.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/Aot.cs @@ -434,6 +434,8 @@ IEnumerable GetAotConfigs () bool RunAotCompiler (string assembliesPath, string aotCompiler, string aotOptions, string assembly, CancellationToken token) { + var stdout_completed = new ManualResetEvent (false); + var stderr_completed = new ManualResetEvent (false); var psi = new ProcessStartInfo () { FileName = aotCompiler, Arguments = aotOptions + " \"" + assembly + "\"", @@ -453,16 +455,32 @@ bool RunAotCompiler (string assembliesPath, string aotCompiler, string aotOption LogDebugMessage ("[AOT] MONO_PATH=\"{0}\" MONO_ENV_OPTIONS=\"{1}\" {2} {3}", psi.EnvironmentVariables ["MONO_PATH"], psi.EnvironmentVariables ["MONO_ENV_OPTIONS"], psi.FileName, psi.Arguments); - var proc = new Process (); - proc.OutputDataReceived += OnAotOutputData; - proc.ErrorDataReceived += OnAotErrorData; - proc.StartInfo = psi; - proc.Start (); - proc.BeginOutputReadLine (); - proc.BeginErrorReadLine (); - token.Register (() => { try { proc.Kill (); } catch (Exception) {} }); - proc.WaitForExit (); - return proc.ExitCode == 0; + using (var proc = new Process ()) { + proc.OutputDataReceived += (s, e) => { + if (e.Data != null) + OnAotOutputData (s, e); + else + stdout_completed.Set (); + }; + proc.ErrorDataReceived += (s, e) => { + if (e.Data != null) + OnAotErrorData (s, e); + else + stderr_completed.Set (); + }; + proc.StartInfo = psi; + proc.Start (); + proc.BeginOutputReadLine (); + proc.BeginErrorReadLine (); + token.Register (() => { try { proc.Kill (); } catch (Exception) { } }); + proc.WaitForExit (); + if (psi.RedirectStandardError) + stderr_completed.WaitOne (TimeSpan.FromSeconds (30)); + if (psi.RedirectStandardOutput) + stdout_completed.WaitOne (TimeSpan.FromSeconds (30)); + return proc.ExitCode == 0; + } + GC.Collect (); } void OnAotOutputData (object sender, DataReceivedEventArgs e) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs b/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs index 2246b7c75a7..54d2d6fbe85 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs @@ -116,34 +116,44 @@ void ExecuteWithAbi (string supportedAbis, string apkInputPath, string apkOutput ArchiveFileList files = new ArchiveFileList (); if (apkInputPath != null) File.Copy (apkInputPath, apkOutputPath + "new", overwrite: true); - using (var apk = ZipArchive.Open (apkOutputPath + "new", apkInputPath != null ? FileMode.Open : FileMode.Create )) { - apk.AddEntry ("NOTICE", + using (var apk = new ZipArchiveEx (apkOutputPath + "new", apkInputPath != null ? FileMode.Open : FileMode.Create )) { + apk.Archive.AddEntry ("NOTICE", Assembly.GetExecutingAssembly ().GetManifestResourceStream ("NOTICE.txt")); // Add classes.dx - apk.AddFiles (DalvikClasses, useFileDirectories: false); + apk.Archive.AddFiles (DalvikClasses, useFileDirectories: false); if (EmbedAssemblies && !BundleAssemblies) AddAssemblies (apk); AddEnvironment (apk); AddRuntimeLibraries (apk, supportedAbis); + apk.Flush(); AddNativeLibraries (files, supportedAbis); + apk.Flush(); AddAdditionalNativeLibraries (files, supportedAbis); + apk.Flush(); AddNativeLibrariesFromAssemblies (apk, supportedAbis); + apk.Flush(); foreach (ITaskItem typemap in TypeMappings) { - apk.AddFile (typemap.ItemSpec, Path.GetFileName(typemap.ItemSpec), compressionMethod: CompressionMethod.Store); + apk.Archive.AddFile (typemap.ItemSpec, Path.GetFileName(typemap.ItemSpec), compressionMethod: CompressionMethod.Store); } + int count = 0; foreach (var file in files) { var item = Path.Combine (file.Item2, Path.GetFileName (file.Item1)) .Replace (Path.DirectorySeparatorChar, '/'); - if (apk.ContainsEntry (item)) { + if (apk.Archive.ContainsEntry (item)) { Log.LogWarning (null, "XA4301", null, file.Item1, 0, 0, 0, 0, "Apk already contains the item {0}; ignoring.", item); continue; } - apk.AddFile (file.Item1, item); + apk.Archive.AddFile (file.Item1, item); + count++; + if (count == ZipArchiveEx.ZipFlushLimit) { + apk.Flush(); + count = 0; + } } if (_Debug) AddGdbservers (apk, files, supportedAbis, debugServer); @@ -160,6 +170,7 @@ void ExecuteWithAbi (string supportedAbis, string apkInputPath, string apkOutput var jarFilePaths = libraryProjectJars.Concat (jarFiles != null ? jarFiles.Select (j => j.ItemSpec) : Enumerable.Empty ()); jarFilePaths = MonoAndroidHelper.DistinctFilesByContent (jarFilePaths); + count = 0; foreach (var jarFile in jarFilePaths) { using (var jar = ZipArchive.Open (File.OpenRead (jarFile))) { foreach (var jarItem in jar.Where (ze => !ze.IsDirectory && !ze.FullName.StartsWith ("META-INF") && !ze.FullName.EndsWith (".class") && !ze.FullName.EndsWith (".java") && !ze.FullName.EndsWith ("MANIFEST.MF"))) { @@ -168,15 +179,20 @@ void ExecuteWithAbi (string supportedAbis, string apkInputPath, string apkOutput jarItem.Extract (d); data = d.ToArray (); } - if (apk.Any (e => e.FullName == jarItem.FullName)) + if (apk.Archive.Any (e => e.FullName == jarItem.FullName)) Log.LogMessage ("Warning: failed to add jar entry {0} from {1}: the same file already exists in the apk", jarItem.FullName, Path.GetFileName (jarFile)); else - apk.AddEntry (data, jarItem.FullName); + apk.Archive.AddEntry (data, jarItem.FullName); } } + count++; + if (count == ZipArchiveEx.ZipFlushLimit) { + apk.Flush(); + count = 0; + } } if (StubApplicationDataFile != null && File.Exists (StubApplicationDataFile)) - apk.AddFile (StubApplicationDataFile, Path.GetFileName (StubApplicationDataFile)); + apk.Archive.AddFile (StubApplicationDataFile, Path.GetFileName (StubApplicationDataFile)); } MonoAndroidHelper.CopyIfZipChanged (apkOutputPath + "new", apkOutputPath); File.Delete (apkOutputPath + "new"); @@ -249,14 +265,15 @@ public override bool Execute () return !Log.HasLoggedErrors; } - private void AddAssemblies (ZipArchive apk) + private void AddAssemblies (ZipArchiveEx apk) { bool debug = _Debug; bool use_shared_runtime = String.Equals (UseSharedRuntime, "true", StringComparison.OrdinalIgnoreCase); + int count = 0; foreach (ITaskItem assembly in ResolvedUserAssemblies) { // Add assembly - apk.AddFile (assembly.ItemSpec, GetTargetDirectory (assembly.ItemSpec) + "/" + Path.GetFileName (assembly.ItemSpec), compressionMethod: CompressionMethod.Store); + apk.Archive.AddFile (assembly.ItemSpec, GetTargetDirectory (assembly.ItemSpec) + "/" + Path.GetFileName (assembly.ItemSpec), compressionMethod: CompressionMethod.Store); // Try to add config if exists var config = Path.ChangeExtension (assembly.ItemSpec, "dll.config"); @@ -267,21 +284,27 @@ private void AddAssemblies (ZipArchive apk) var symbols = Path.ChangeExtension (assembly.ItemSpec, "dll.mdb"); if (File.Exists (symbols)) - apk.AddFile (symbols, "assemblies/" + Path.GetFileName (symbols), compressionMethod: CompressionMethod.Store); + apk.Archive.AddFile (symbols, "assemblies/" + Path.GetFileName (symbols), compressionMethod: CompressionMethod.Store); symbols = Path.ChangeExtension (assembly.ItemSpec, "pdb"); if (File.Exists (symbols)) - apk.AddFile (symbols, "assemblies/" + Path.GetFileName (symbols), compressionMethod: CompressionMethod.Store); + apk.Archive.AddFile (symbols, "assemblies/" + Path.GetFileName (symbols), compressionMethod: CompressionMethod.Store); + } + count++; + if (count == ZipArchiveEx.ZipFlushLimit) { + apk.Flush(); + count = 0; } } if (use_shared_runtime) return; + count = 0; // Add framework assemblies foreach (ITaskItem assembly in ResolvedFrameworkAssemblies) { - apk.AddFile (assembly.ItemSpec, "assemblies/" + Path.GetFileName (assembly.ItemSpec), compressionMethod: CompressionMethod.Store); + apk.Archive.AddFile (assembly.ItemSpec, "assemblies/" + Path.GetFileName (assembly.ItemSpec), compressionMethod: CompressionMethod.Store); var config = Path.ChangeExtension (assembly.ItemSpec, "dll.config"); AddAssemblyConfigEntry (apk, config); // Try to add symbols if Debug @@ -289,17 +312,22 @@ private void AddAssemblies (ZipArchive apk) var symbols = Path.ChangeExtension (assembly.ItemSpec, "dll.mdb"); if (File.Exists (symbols)) - apk.AddFile (symbols, "assemblies/" + Path.GetFileName (symbols), compressionMethod: CompressionMethod.Store); + apk.Archive.AddFile (symbols, "assemblies/" + Path.GetFileName (symbols), compressionMethod: CompressionMethod.Store); symbols = Path.ChangeExtension (assembly.ItemSpec, "pdb"); if (File.Exists (symbols)) - apk.AddFile (symbols, "assemblies/" + Path.GetFileName (symbols), compressionMethod: CompressionMethod.Store); + apk.Archive.AddFile (symbols, "assemblies/" + Path.GetFileName (symbols), compressionMethod: CompressionMethod.Store); + } + count++; + if (count == ZipArchiveEx.ZipFlushLimit) { + apk.Flush(); + count = 0; } } } - void AddAssemblyConfigEntry (ZipArchive apk, string configFile) + void AddAssemblyConfigEntry (ZipArchiveEx apk, string configFile) { if (!File.Exists (configFile)) return; @@ -309,7 +337,7 @@ void AddAssemblyConfigEntry (ZipArchive apk, string configFile) source.CopyTo (dest); dest.WriteByte (0); dest.Position = 0; - apk.AddEntry ("assemblies/" + Path.GetFileName (configFile), dest, compressionMethod: CompressionMethod.Store); + apk.Archive.AddEntry ("assemblies/" + Path.GetFileName (configFile), dest, compressionMethod: CompressionMethod.Store); } } @@ -322,7 +350,7 @@ static string GetTargetDirectory (string path) return "assemblies"; } - void AddEnvironment (ZipArchive apk) + void AddEnvironment (ZipArchiveEx apk) { var environment = new StringWriter () { NewLine = "\n", @@ -395,7 +423,7 @@ void AddEnvironment (ZipArchive apk) environment.WriteLine ("MONO_GC_PARAMS=major=marksweep"); } - apk.AddEntry ("environment", environment.ToString (), + apk.Archive.AddEntry ("environment", environment.ToString (), new UTF8Encoding (encoderShouldEmitUTF8Identifier:false)); } @@ -431,13 +459,13 @@ HashSet ParseProfilers (string value) return results; } - void AddNativeLibrary (ZipArchive apk, string abi, string filename) + void AddNativeLibrary (ZipArchiveEx apk, string abi, string filename) { var path = Path.Combine (MSBuildXamarinAndroidDirectory, "lib", abi, filename); - apk.AddEntry (string.Format ("lib/{0}/{1}", abi, filename), File.OpenRead (path)); + apk.Archive.AddEntry (string.Format ("lib/{0}/{1}", abi, filename), File.OpenRead (path)); } - void AddProfilers (ZipArchive apk, string abi) + void AddProfilers (ZipArchiveEx apk, string abi) { if (!string.IsNullOrEmpty (AndroidEmbedProfilers)) { foreach (var profiler in ParseProfilers (AndroidEmbedProfilers)) { @@ -447,7 +475,7 @@ void AddProfilers (ZipArchive apk, string abi) } } - void AddBtlsLibs (ZipArchive apk, string abi) + void AddBtlsLibs (ZipArchiveEx apk, string abi) { if (string.Compare ("btls", TlsProvider, StringComparison.OrdinalIgnoreCase) == 0) { AddNativeLibrary (apk, abi, "libmono-btls-shared.so"); @@ -457,14 +485,14 @@ void AddBtlsLibs (ZipArchive apk, string abi) // * "legacy": } - void AddRuntimeLibraries (ZipArchive apk, string supportedAbis) + void AddRuntimeLibraries (ZipArchiveEx apk, string supportedAbis) { bool use_shared_runtime = String.Equals (UseSharedRuntime, "true", StringComparison.OrdinalIgnoreCase); var abis = supportedAbis.Split (new char[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries); foreach (var abi in abis) { string library = string.Format ("libmono-android.{0}.so", _Debug ? "debug" : "release"); var path = Path.Combine (MSBuildXamarinAndroidDirectory, "lib", abi, library); - apk.AddEntry (string.Format ("lib/{0}/libmonodroid.so", abi), File.OpenRead (path)); + apk.Archive.AddEntry (string.Format ("lib/{0}/libmonodroid.so", abi), File.OpenRead (path)); if (!use_shared_runtime) { // include the sgen AddNativeLibrary (apk, abi, "libmonosgen-2.0.so"); @@ -474,8 +502,9 @@ void AddRuntimeLibraries (ZipArchive apk, string supportedAbis) } } - void AddNativeLibrariesFromAssemblies (ZipArchive apk, string supportedAbis) + void AddNativeLibrariesFromAssemblies (ZipArchiveEx apk, string supportedAbis) { + int count = 0; var abis = supportedAbis.Split (new char[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries); using (var res = new DirectoryAssemblyResolver (Console.WriteLine, loadDebugSymbols: false)) { foreach (var assembly in EmbeddedNativeLibraryAssemblies) @@ -493,19 +522,24 @@ void AddNativeLibrariesFromAssemblies (ZipArchive apk, string supportedAbis) if (e.IsDirectory) continue; var key = e.FullName.Replace ("native_library_imports", "lib"); - if (apk.Any (k => k.FullName == key)) { + if (apk.Archive.Any (k => k.FullName == key)) { Log.LogCodedWarning ("4301", "Apk already contains the item {0}; ignoring.", key); continue; } using (var s = new MemoryStream ()) { e.Extract (s); s.Position = 0; - apk.AddEntry (s.ToArray (), key); + apk.Archive.AddEntry (s.ToArray (), key); } } } } } + count++; + if (count == ZipArchiveEx.ZipFlushLimit) { + apk.Flush(); + count = 0; + } } } } @@ -566,11 +600,12 @@ void AddNativeLibrary (ArchiveFileList files, string path, string abi) files.Add (new Tuple (path, string.Format ("lib/{0}", abi))); } - private void AddGdbservers (ZipArchive apk, ArchiveFileList files, string supportedAbis, AndroidDebugServer debugServer) + private void AddGdbservers (ZipArchiveEx apk, ArchiveFileList files, string supportedAbis, AndroidDebugServer debugServer) { if (string.IsNullOrEmpty (AndroidNdkDirectory)) return; + int count = 0; foreach (var sabi in supportedAbis.Split (new char[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries)) { var arch = GdbPaths.GetArchFromAbi (sabi); var abi = GdbPaths.GetAbiFromArch (arch); @@ -585,7 +620,12 @@ private void AddGdbservers (ZipArchive apk, ArchiveFileList files, string suppor if (!File.Exists (debugServerPath)) continue; Log.LogDebugMessage ("Adding {0} debug server '{1}' to the APK as '{2}'", sabi, debugServerPath, entryName); - apk.AddEntry (entryName, File.OpenRead (debugServerPath)); + apk.Archive.AddEntry (entryName, File.OpenRead (debugServerPath)); + count++; + if (count == ZipArchiveEx.ZipFlushLimit) { + apk.Flush(); + count = 0; + } } } } 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 a86d63ac458..70a4545bec9 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 @@ -1,6 +1,7 @@ using System; using System.IO; using System.Linq; +using System.Text; using System.Xml.Linq; using Microsoft.Build.Framework; using NUnit.Framework; @@ -195,6 +196,101 @@ public void BuildInDesignTimeMode () Assert.IsTrue (builder.Output.IsTargetSkipped ("_ResolveLibraryProjectImports"), "target \"_ResolveLibraryProjectImports\' should have been skipped."); } } + + [Test] + public void BuildAMassiveApp() + { + var testPath = Path.Combine("temp", "BuildAMassiveApp"); + TestContext.CurrentContext.Test.Properties ["Output"] = new string [] { Path.Combine (Root, testPath) }; + var sb = new SolutionBuilder("BuildAMassiveApp.sln") { + SolutionPath = Path.Combine(Root, testPath), + Verbosity = LoggerVerbosity.Diagnostic, + }; + var app1 = new XamarinAndroidApplicationProject() { + ProjectName = "App1", + AotAssemblies = true, + IsRelease = true, + Packages = { + KnownPackages.AndroidSupportV4_21_0_3_0, + KnownPackages.GooglePlayServices_22_0_0_2, + }, + }; + app1.Imports.Add (new Import ("foo.targets") { + TextContent = () => @" + + + + armeabi-v7a;x86 + $(AndroidSupportedAbis);armeabi + $(AndroidSupportedAbis);arm64-v8a + $(AndroidSupportedAbis);x86_64 + + + + + + False + + + + +", + }); + app1.SetProperty(KnownProperties.AndroidUseSharedRuntime, "False"); + sb.Projects.Add(app1); + var code = new StringBuilder(); + code.AppendLine("using System;"); + code.AppendLine("namespace App1 {"); + code.AppendLine("\tpublic class AppCode {"); + code.AppendLine("\t\tpublic void Foo () {"); + for (int i = 0; i < 128; i++) { + var libName = $"Lib{i}"; + var lib = new XamarinAndroidLibraryProject() { + ProjectName = libName, + IsRelease = true, + OtherBuildItems = { + new AndroidItem.AndroidAsset ($"Assets\\{libName}.txt") { + TextContent = () => "Asset1", + Encoding = Encoding.ASCII, + }, + new AndroidItem.AndroidAsset ($"Assets\\subfolder\\{libName}.txt") { + TextContent = () => "Asset2", + Encoding = Encoding.ASCII, + }, + }, + Sources = { + new BuildItem.Source ($"{libName}.cs") { + TextContent = () => @"using System; + +namespace "+ libName + @" { + + public class " + libName + @" { + public static void Foo () { + } + } +}" + }, + } + }; + var strings = lib.AndroidResources.First(x => x.Include() == "Resources\\values\\Strings.xml"); + strings.TextContent = () => @" + + " + libName + @" +"; + sb.Projects.Add(lib); + app1.References.Add(new BuildItem.ProjectReference($"..\\{libName}\\{libName}.csproj", libName, lib.ProjectGuid)); + code.AppendLine($"\t\t\t{libName}.{libName}.Foo ();"); + } + code.AppendLine("\t\t}"); + code.AppendLine("\t}"); + code.AppendLine("}"); + app1.Sources.Add(new BuildItem.Source("Code.cs") { + TextContent = ()=> code.ToString (), + }); + Assert.IsTrue(sb.Build(new string[] { "Configuration=Release" }), "Solution should have built."); + Assert.IsTrue(sb.BuildProject(app1, "SignAndroidPackage"), "Build of project should have succeeded"); + sb.Dispose(); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/IncrementalBuildTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/IncrementalBuildTest.cs index 865556fb88f..3c35dea1417 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/IncrementalBuildTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/IncrementalBuildTest.cs @@ -17,8 +17,8 @@ public class IncrementalBuildTest : BaseTest public void AllProjectsHaveSameOutputDirectory() { var testPath = Path.Combine ("temp", "AllProjectsHaveSameOutputDirectory"); - var sb = new SolutionBuilder () { - SolutionPath = Path.Combine (Root, testPath, "AllProjectsHaveSameOutputDirectory.sln"), + var sb = new SolutionBuilder("AllProjectsHaveSameOutputDirectory.sln") { + SolutionPath = Path.Combine (Root, testPath), Verbosity = LoggerVerbosity.Diagnostic, }; diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/SolutionBuilder.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/SolutionBuilder.cs index 22d9556f77e..3abae105911 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/SolutionBuilder.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/SolutionBuilder.cs @@ -10,17 +10,19 @@ public class SolutionBuilder : Builder { public IList Projects { get; } public string SolutionPath { get; set; } + public string SolutionName { get; set; } public bool BuildSucceeded { get; set; } - public SolutionBuilder () : base() + public SolutionBuilder (string solutionName) : base() { + SolutionName = solutionName; Projects = new List (); } public void Save () { foreach (var p in Projects) { - using (var pb = new ProjectBuilder (Path.Combine (Path.GetDirectoryName (SolutionPath), p.ProjectName))) { + using (var pb = new ProjectBuilder (Path.Combine (SolutionPath, p.ProjectName))) { pb.Save (p); } } @@ -47,27 +49,33 @@ public void Save () } sb.Append ("\tEndGlobalSection\n"); sb.Append ("EndGlobal\n"); - File.WriteAllText (SolutionPath, sb.ToString ()); + File.WriteAllText (Path.Combine (SolutionPath, SolutionName), sb.ToString ()); } - public bool Build () + public bool BuildProject(XamarinProject project, string target = "Build") + { + BuildSucceeded = BuildInternal(Path.Combine (SolutionPath, project.ProjectName, project.ProjectFilePath), target); + return BuildSucceeded; + } + + public bool Build (params string[] parameters) { Save (); - BuildSucceeded = BuildInternal (SolutionPath, "Build"); + BuildSucceeded = BuildInternal (Path.Combine (SolutionPath, SolutionName), "Build", parameters); return BuildSucceeded; } - public bool ReBuild () + public bool ReBuild(params string[] parameters) { Save (); - BuildSucceeded = BuildInternal (SolutionPath, "ReBuild"); + BuildSucceeded = BuildInternal(Path.Combine(SolutionPath, SolutionName), "ReBuild", parameters); return BuildSucceeded; } - public bool Clean () + public bool Clean(params string[] parameters) { Save (); - BuildSucceeded = BuildInternal (SolutionPath, "Clean"); + BuildSucceeded = BuildInternal(Path.Combine(SolutionPath, SolutionName), "Clean", parameters); return BuildSucceeded; } @@ -75,7 +83,7 @@ protected override void Dispose (bool disposing) { if (disposing) if (BuildSucceeded) - Directory.Delete (Path.GetDirectoryName (SolutionPath), recursive: true); + Directory.Delete (SolutionPath, recursive: true); } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ZipArchiveEx.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ZipArchiveEx.cs index 80aa16f051e..b9c7d3571b2 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ZipArchiveEx.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ZipArchiveEx.cs @@ -1,21 +1,33 @@ using System; using System.IO; +using System.Linq; using Xamarin.Tools.Zip; namespace Xamarin.Android.Tasks { public class ZipArchiveEx : IDisposable { + + public static int ZipFlushLimit = 50; + ZipArchive zip; string archive; - public ZipArchiveEx (string archive) + public ZipArchive Archive { + get { return zip; } + } + + public ZipArchiveEx (string archive) : this (archive, FileMode.CreateNew) + { + } + + public ZipArchiveEx(string archive, FileMode filemode) { this.archive = archive; - zip = ZipArchive.Open (archive, FileMode.CreateNew); + zip = ZipArchive.Open(archive, filemode); } - void Flush () + public void Flush () { if (zip != null) { zip.Close (); @@ -51,7 +63,7 @@ void AddFiles (string folder, string folderInArchive) continue; zip.AddFile (fileName, ArchiveNameForFile (fileName, folderInArchive)); count++; - if (count == 50) { + if (count == ZipArchiveEx.ZipFlushLimit) { Flush (); count = 0; }