diff --git a/src/Xamarin.Android.Build.Tasks/Generator/Generator.cs b/src/Xamarin.Android.Build.Tasks/Generator/Generator.cs index 23d005a25b3..c1e35826510 100644 --- a/src/Xamarin.Android.Build.Tasks/Generator/Generator.cs +++ b/src/Xamarin.Android.Build.Tasks/Generator/Generator.cs @@ -7,6 +7,7 @@ using Java.Interop.Tools.Diagnostics; using Java.Interop.Tools.JavaCallableWrappers; +using System.IO; namespace Xamarin.Android.Tasks { @@ -15,45 +16,47 @@ class Generator public static bool CreateJavaSources (TaskLoggingHelper log, IEnumerable javaTypes, string outputPath, string applicationJavaClass, bool useSharedRuntime, bool generateOnCreateOverrides, bool hasExportReference) { bool ok = true; - foreach (var t in javaTypes) { - try { - GenerateJavaSource (log, t, outputPath, applicationJavaClass, useSharedRuntime, generateOnCreateOverrides, hasExportReference); - } catch (XamarinAndroidException xae) { - ok = false; - log.LogError ( - subcategory: "", - errorCode: "XA" + xae.Code, - helpKeyword: string.Empty, - file: xae.SourceFile, - lineNumber: xae.SourceLine, - columnNumber: 0, - endLineNumber: 0, - endColumnNumber: 0, - message: xae.MessageWithoutCode, - messageArgs: new object [0] - ); - } - } - return ok; - } + using (var memoryStream = new MemoryStream ()) + using (var writer = new StreamWriter (memoryStream)) { + foreach (var t in javaTypes) { + //Reset for reuse + memoryStream.SetLength (0); - static void GenerateJavaSource (TaskLoggingHelper log, TypeDefinition t, string outputPath, string applicationJavaClass, bool useSharedRuntime, bool generateOnCreateOverrides, bool hasExportReference) - { - try { - var jti = new JavaCallableWrapperGenerator (t, log.LogWarning) { - UseSharedRuntime = useSharedRuntime, - GenerateOnCreateOverrides = generateOnCreateOverrides, - ApplicationJavaClass = applicationJavaClass, - }; + try { + var jti = new JavaCallableWrapperGenerator (t, log.LogWarning) { + UseSharedRuntime = useSharedRuntime, + GenerateOnCreateOverrides = generateOnCreateOverrides, + ApplicationJavaClass = applicationJavaClass, + }; + + jti.Generate (writer); + writer.Flush (); - jti.Generate (outputPath); - if (jti.HasExport && !hasExportReference) - Diagnostic.Error (4210, "You need to add a reference to Mono.Android.Export.dll when you use ExportAttribute or ExportFieldAttribute."); - } catch (Exception ex) { - if (ex is XamarinAndroidException) - throw; - Diagnostic.Error (4209, "Failed to create JavaTypeInfo for class: {0} due to {1}", t.FullName, ex); + var path = jti.GetDestinationPath (outputPath); + MonoAndroidHelper.CopyIfStreamChanged (memoryStream, path); + if (jti.HasExport && !hasExportReference) + Diagnostic.Error (4210, "You need to add a reference to Mono.Android.Export.dll when you use ExportAttribute or ExportFieldAttribute."); + } catch (XamarinAndroidException xae) { + ok = false; + log.LogError ( + subcategory: "", + errorCode: "XA" + xae.Code, + helpKeyword: string.Empty, + file: xae.SourceFile, + lineNumber: xae.SourceLine, + columnNumber: 0, + endLineNumber: 0, + endColumnNumber: 0, + message: xae.MessageWithoutCode, + messageArgs: new object [0] + ); + } catch (Exception ex) { + ok = false; + Diagnostic.Error (4209, "Failed to create JavaTypeInfo for class: {0} due to {1}", t.FullName, ex); + } + } } + return ok; } } } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index 37ff74999ec..8ab08c7d297 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -76,8 +76,7 @@ public override bool Execute () using (var res = new DirectoryAssemblyResolver (this.CreateTaskLogger (), loadDebugSymbols: true)) { Run (res); } - } - catch (XamarinAndroidException e) { + } catch (XamarinAndroidException e) { Log.LogCodedError (string.Format ("XA{0:0000}", e.Code), e.MessageWithoutCode); if (MonoAndroidHelper.LogInternalExceptions) Log.LogMessage (e.ToString ()); @@ -99,8 +98,6 @@ void Run (DirectoryAssemblyResolver res) { PackageNamingPolicy pnp; JavaNativeTypeManager.PackageNamingPolicy = Enum.TryParse (PackageNamingPolicy, out pnp) ? pnp : PackageNamingPolicyEnum.LowercaseHash; - var temp = Path.Combine (Path.GetTempPath (), Path.GetRandomFileName ()); - Directory.CreateDirectory (temp); foreach (var dir in FrameworkDirectories) { if (Directory.Exists (dir.ItemSpec)) @@ -111,9 +108,10 @@ void Run (DirectoryAssemblyResolver res) // Put every assembly we'll need in the resolver foreach (var assembly in ResolvedAssemblies) { - res.Load (Path.GetFullPath (assembly.ItemSpec)); + var assemblyFullPath = Path.GetFullPath (assembly.ItemSpec); + res.Load (assemblyFullPath); if (MonoAndroidHelper.FrameworkAttributeLookupTargets.Any (a => Path.GetFileName (assembly.ItemSpec) == a)) - selectedWhitelistAssemblies.Add (Path.GetFullPath (assembly.ItemSpec)); + selectedWhitelistAssemblies.Add (assemblyFullPath); } // However we only want to look for JLO types in user code @@ -130,76 +128,75 @@ void Run (DirectoryAssemblyResolver res) WriteTypeMappings (all_java_types); - var java_types = all_java_types.Where (t => !JavaTypeScanner.ShouldSkipJavaCallableWrapperGeneration (t)); + var java_types = all_java_types + .Where (t => !JavaTypeScanner.ShouldSkipJavaCallableWrapperGeneration (t)) + .ToArray (); // Step 2 - Generate Java stub code - var keep_going = Generator.CreateJavaSources ( + var success = Generator.CreateJavaSources ( Log, java_types, - temp, + Path.Combine (OutputDirectory, "src"), ApplicationJavaClass, UseSharedRuntime, int.Parse (AndroidSdkPlatform) <= 10, ResolvedAssemblies.Any (assembly => Path.GetFileName (assembly.ItemSpec) == "Mono.Android.Export.dll")); - - var temp_map_file = Path.Combine (temp, "acw-map.temp"); + if (!success) + return; // We need to save a map of .NET type -> ACW type for resource file fixups - var managed = new Dictionary (); - var java = new Dictionary (); - var acw_map = new StreamWriter (temp_map_file); - - foreach (var type in java_types) { - string managedKey = type.FullName.Replace ('/', '.'); - string javaKey = JavaNativeTypeManager.ToJniName (type).Replace ('/', '.'); - - acw_map.WriteLine ("{0};{1}", type.GetPartialAssemblyQualifiedName (), javaKey); - - TypeDefinition conflict; - if (managed.TryGetValue (managedKey, out conflict)) { - Log.LogWarning ( - "Duplicate managed type found! Mappings between managed types and Java types must be unique. " + - "First Type: '{0}'; Second Type: '{1}'.", - conflict.GetAssemblyQualifiedName (), - type.GetAssemblyQualifiedName ()); - Log.LogWarning ( - "References to the type '{0}' will refer to '{1}'.", - managedKey, conflict.GetAssemblyQualifiedName ()); - continue; - } - if (java.TryGetValue (javaKey, out conflict)) { - Log.LogError ( - "Duplicate Java type found! Mappings between managed types and Java types must be unique. " + - "First Type: '{0}'; Second Type: '{1}'", - conflict.GetAssemblyQualifiedName (), - type.GetAssemblyQualifiedName ()); - keep_going = false; - continue; + var managed = new Dictionary (java_types.Length, StringComparer.Ordinal); + var java = new Dictionary (java_types.Length, StringComparer.Ordinal); + // Allocate a MemoryStream with a reasonable guess at its capacity + using (var stream = new MemoryStream (java_types.Length * 32)) + using (var acw_map = new StreamWriter (stream)) { + foreach (var type in java_types) { + string managedKey = type.FullName.Replace ('/', '.'); + string javaKey = JavaNativeTypeManager.ToJniName (type).Replace ('/', '.'); + + acw_map.Write (type.GetPartialAssemblyQualifiedName ()); + acw_map.Write (';'); + acw_map.Write (javaKey); + acw_map.WriteLine (); + + TypeDefinition conflict; + if (managed.TryGetValue (managedKey, out conflict)) { + Log.LogWarning ( + "Duplicate managed type found! Mappings between managed types and Java types must be unique. " + + "First Type: '{0}'; Second Type: '{1}'.", + conflict.GetAssemblyQualifiedName (), + type.GetAssemblyQualifiedName ()); + Log.LogWarning ( + "References to the type '{0}' will refer to '{1}'.", + managedKey, conflict.GetAssemblyQualifiedName ()); + continue; + } + if (java.TryGetValue (javaKey, out conflict)) { + Log.LogError ( + "Duplicate Java type found! Mappings between managed types and Java types must be unique. " + + "First Type: '{0}'; Second Type: '{1}'", + conflict.GetAssemblyQualifiedName (), + type.GetAssemblyQualifiedName ()); + success = false; + continue; + } + + managed.Add (managedKey, type); + java.Add (javaKey, type); + + acw_map.Write (managedKey); + acw_map.Write (';'); + acw_map.Write (javaKey); + acw_map.WriteLine (); + + acw_map.Write (JavaNativeTypeManager.ToCompatJniName (type).Replace ('/', '.')); + acw_map.Write (';'); + acw_map.Write (javaKey); + acw_map.WriteLine (); } - managed.Add (managedKey, type); - java.Add (javaKey, type); - acw_map.WriteLine ("{0};{1}", managedKey, javaKey); - acw_map.WriteLine ("{0};{1}", JavaNativeTypeManager.ToCompatJniName (type).Replace ('/', '.'), javaKey); - } - - acw_map.Close (); - - //The previous steps found an error, so we must abort and not generate any further output - //We must do so subsequent unchanged builds fail too. - if (!keep_going) { - File.Delete (temp_map_file); - return; - } - - MonoAndroidHelper.CopyIfChanged (temp_map_file, AcwMapFile); - - try { File.Delete (temp_map_file); } catch (Exception) { } - - // Only overwrite files if the contents actually changed - foreach (var file in Directory.GetFiles (temp, "*", SearchOption.AllDirectories)) { - var dest = Path.GetFullPath (Path.Combine (OutputDirectory, "src", file.Substring (temp.Length + 1))); - MonoAndroidHelper.CopyIfChanged (file, dest); + acw_map.Flush (); + MonoAndroidHelper.CopyIfStreamChanged (stream, AcwMapFile); } // Step 3 - Merge [Activity] and friends into AndroidManifest.xml @@ -219,38 +216,24 @@ void Run (DirectoryAssemblyResolver res) var additionalProviders = manifest.Merge (all_java_types, selectedWhitelistAssemblies, ApplicationJavaClass, EmbedAssemblies, BundledWearApplicationName, MergedManifestDocuments); - var temp_manifest = Path.Combine (temp, "AndroidManifest.xml"); - var real_manifest = Path.GetFullPath (MergedAndroidManifestOutput); + using (var stream = new MemoryStream ()) { + manifest.Save (stream); - manifest.Save (temp_manifest); - - // Only write the new manifest if it actually changed - MonoAndroidHelper.CopyIfChanged (temp_manifest, real_manifest); + // Only write the new manifest if it actually changed + MonoAndroidHelper.CopyIfStreamChanged (stream, MergedAndroidManifestOutput); + } // Create additional runtime provider java sources. string providerTemplateFile = UseSharedRuntime ? "MonoRuntimeProvider.Shared.java" : "MonoRuntimeProvider.Bundled.java"; - string providerTemplate = new StreamReader (typeof (JavaCallableWrapperGenerator).Assembly.GetManifestResourceStream (providerTemplateFile)).ReadToEnd (); + string providerTemplate = GetResource (providerTemplateFile); foreach (var provider in additionalProviders) { - var temp_provider = Path.Combine (temp, provider + ".java"); - File.WriteAllText (temp_provider, providerTemplate.Replace ("MonoRuntimeProvider", provider)); - var real_provider_dir = Path.GetFullPath (Path.Combine (OutputDirectory, "src", "mono")); - Directory.CreateDirectory (real_provider_dir); - var real_provider = Path.Combine (real_provider_dir, provider + ".java"); - MonoAndroidHelper.CopyIfChanged (temp_provider, real_provider); + var contents = providerTemplate.Replace ("MonoRuntimeProvider", provider); + var real_provider = Path.Combine (OutputDirectory, "src", "mono", provider + ".java"); + MonoAndroidHelper.CopyIfStringChanged (contents, real_provider); } // Create additional application java sources. - - Action> save = (resource, filename, destDir, applyTemplate) => { - string temp_file = Path.Combine (temp, filename); - string template = applyTemplate (new StreamReader (typeof (GenerateJavaStubs).Assembly.GetManifestResourceStream (resource)).ReadToEnd ()); - File.WriteAllText (temp_file, template); - Directory.CreateDirectory (destDir); - var real_file = Path.Combine (destDir, filename); - MonoAndroidHelper.CopyIfChanged (temp_file, real_file); - }; - StringWriter regCallsWriter = new StringWriter (); regCallsWriter.WriteLine ("\t\t// Application and Instrumentation ACWs must be registered first."); foreach (var type in java_types) { @@ -262,17 +245,28 @@ void Run (DirectoryAssemblyResolver res) } regCallsWriter.Close (); - var real_app_dir = Path.GetFullPath (Path.Combine (OutputDirectory, "src", "mono", "android", "app")); + var real_app_dir = Path.Combine (OutputDirectory, "src", "mono", "android", "app"); string applicationTemplateFile = "ApplicationRegistration.java"; - save (applicationTemplateFile, applicationTemplateFile, real_app_dir, + SaveResource (applicationTemplateFile, applicationTemplateFile, real_app_dir, template => template.Replace ("// REGISTER_APPLICATION_AND_INSTRUMENTATION_CLASSES_HERE", regCallsWriter.ToString ())); // Create NotifyTimeZoneChanges java sources. string notifyTimeZoneChangesFile = "NotifyTimeZoneChanges.java"; - save (notifyTimeZoneChangesFile, notifyTimeZoneChangesFile, real_app_dir, template => template); - - // Delete our temp directory - try { Directory.Delete (temp, true); } catch (Exception) { } + SaveResource (notifyTimeZoneChangesFile, notifyTimeZoneChangesFile, real_app_dir, template => template); + } + + string GetResource (string resource) + { + using (var stream = typeof (T).Assembly.GetManifestResourceStream (resource)) + using (var reader = new StreamReader (stream)) + return reader.ReadToEnd (); + } + + void SaveResource (string resource, string filename, string destDir, Func applyTemplate) + { + string template = GetResource (resource); + template = applyTemplate (template); + MonoAndroidHelper.CopyIfStringChanged (template, Path.Combine (destDir, filename)); } void WriteTypeMappings (List types) @@ -287,11 +281,10 @@ void WriteTypeMappings (List types) void UpdateWhenChanged (string path, Action generator) { - var np = path + ".new"; - using (var o = File.OpenWrite (np)) - generator (o); - MonoAndroidHelper.CopyIfChanged (np, path); - File.Delete (np); + using (var stream = new MemoryStream ()) { + generator (stream); + MonoAndroidHelper.CopyIfStreamChanged (stream, path); + } } } } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateResourceDesigner.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateResourceDesigner.cs index 5201c8792c0..889380d290c 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateResourceDesigner.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateResourceDesigner.cs @@ -239,12 +239,8 @@ private void WriteFile (string file, CodeTypeDeclaration resources, string langu } } } - - var temp_o = Path.Combine (Path.GetDirectoryName (file), "__" + Path.GetFileName (file) + ".new"); - using (TextWriter o = File.CreateText (temp_o)) - o.Write (code); - MonoAndroidHelper.CopyIfChanged (temp_o, file); - try { File.Delete (temp_o); } catch (Exception) { } + + MonoAndroidHelper.CopyIfStringChanged (code, file); } private void AddRename (string android, string user) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/MonoAndroidHelperTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/MonoAndroidHelperTests.cs new file mode 100644 index 00000000000..ea92de8f776 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/MonoAndroidHelperTests.cs @@ -0,0 +1,136 @@ +using System.IO; +using NUnit.Framework; +using Xamarin.Android.Tasks; + +namespace Xamarin.Android.Build.Tests +{ + [TestFixture] + public class MonoAndroidHelperTests + { + string temp; + + [SetUp] + public void SetUp () + { + temp = Path.Combine (Path.GetTempPath (), TestContext.CurrentContext.Test.Name); + } + + [TearDown] + public void TearDown () + { + File.Delete (temp); + } + + [Test] + public void CopyIfStringChanged () + { + var foo = "bar"; + Assert.IsTrue (MonoAndroidHelper.CopyIfStringChanged (foo, temp), "Should write on new file."); + FileAssert.Exists (temp); + Assert.IsFalse (MonoAndroidHelper.CopyIfStringChanged (foo, temp), "Should *not* write unless changed."); + foo += "\n"; + Assert.IsTrue (MonoAndroidHelper.CopyIfStringChanged (foo, temp), "Should write when changed."); + } + + [Test] + public void CopyIfBytesChanged () + { + var foo = new byte [32]; + Assert.IsTrue (MonoAndroidHelper.CopyIfBytesChanged (foo, temp), "Should write on new file."); + FileAssert.Exists (temp); + Assert.IsFalse (MonoAndroidHelper.CopyIfBytesChanged (foo, temp), "Should *not* write unless changed."); + foo [0] = 0xFF; + Assert.IsTrue (MonoAndroidHelper.CopyIfBytesChanged (foo, temp), "Should write when changed."); + } + + [Test] + public void CopyIfStreamChanged () + { + using (var foo = new MemoryStream ()) + using (var writer = new StreamWriter (foo)) { + writer.WriteLine ("bar"); + writer.Flush (); + + Assert.IsTrue (MonoAndroidHelper.CopyIfStreamChanged (foo, temp), "Should write on new file."); + FileAssert.Exists (temp); + Assert.IsFalse (MonoAndroidHelper.CopyIfStreamChanged (foo, temp), "Should *not* write unless changed."); + writer.WriteLine (); + writer.Flush (); + Assert.IsTrue (MonoAndroidHelper.CopyIfStreamChanged (foo, temp), "Should write when changed."); + } + } + + [Test] + public void CopyIfStringChanged_NewDirectory () + { + temp = Path.Combine (temp, "foo.txt"); + + var foo = "bar"; + Assert.IsTrue (MonoAndroidHelper.CopyIfStringChanged (foo, temp), "Should write on new file."); + FileAssert.Exists (temp); + } + + [Test] + public void CopyIfBytesChanged_NewDirectory () + { + temp = Path.Combine (temp, "foo.bin"); + + var foo = new byte [32]; + Assert.IsTrue (MonoAndroidHelper.CopyIfBytesChanged (foo, temp), "Should write on new file."); + FileAssert.Exists (temp); + } + + [Test] + public void CopyIfStreamChanged_NewDirectory () + { + temp = Path.Combine (temp, "foo.txt"); + + using (var foo = new MemoryStream ()) + using (var writer = new StreamWriter (foo)) { + writer.WriteLine ("bar"); + writer.Flush (); + + Assert.IsTrue (MonoAndroidHelper.CopyIfStreamChanged (foo, temp), "Should write on new file."); + FileAssert.Exists (temp); + } + } + + [Test] + public void CopyIfStringChanged_Readonly () + { + File.WriteAllText (temp, ""); + File.SetAttributes (temp, FileAttributes.ReadOnly); + + var foo = "bar"; + Assert.IsTrue (MonoAndroidHelper.CopyIfStringChanged (foo, temp), "Should write on new file."); + FileAssert.Exists (temp); + } + + [Test] + public void CopyIfBytesChanged_Readonly () + { + File.WriteAllText (temp, ""); + File.SetAttributes (temp, FileAttributes.ReadOnly); + + var foo = new byte [32]; + Assert.IsTrue (MonoAndroidHelper.CopyIfBytesChanged (foo, temp), "Should write on new file."); + FileAssert.Exists (temp); + } + + [Test] + public void CopyIfStreamChanged_Readonly () + { + File.WriteAllText (temp, ""); + File.SetAttributes (temp, FileAttributes.ReadOnly); + + using (var foo = new MemoryStream ()) + using (var writer = new StreamWriter (foo)) { + writer.WriteLine ("bar"); + writer.Flush (); + + Assert.IsTrue (MonoAndroidHelper.CopyIfStreamChanged (foo, temp), "Should write on new file."); + FileAssert.Exists (temp); + } + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Xamarin.Android.Build.Tests.Shared.projitems b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Xamarin.Android.Build.Tests.Shared.projitems index 4caedf0eed4..ac351869cfb 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Xamarin.Android.Build.Tests.Shared.projitems +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Xamarin.Android.Build.Tests.Shared.projitems @@ -20,6 +20,7 @@ + diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/Files.cs b/src/Xamarin.Android.Build.Tasks/Utilities/Files.cs index 4a8da6df2ea..8011c80f397 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/Files.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/Files.cs @@ -4,6 +4,7 @@ using Xamarin.Tools.Zip; using System.Collections.Generic; +using System.Text; #if MSBUILD using Microsoft.Build.Utilities; using Xamarin.Android.Tasks; @@ -72,6 +73,44 @@ public static bool CopyIfChanged (string source, string destination) return false; } + public static bool CopyIfStringChanged (string contents, string destination) + { + //NOTE: this is not optimal since it allocates a byte[]. We can improve this down the road with Span or System.Buffers. + var bytes = Encoding.UTF8.GetBytes (contents); + return CopyIfBytesChanged (bytes, destination); + } + + public static bool CopyIfBytesChanged (byte[] bytes, string destination) + { + if (HasBytesChanged (bytes, destination)) { + var directory = Path.GetDirectoryName (destination); + if (!string.IsNullOrEmpty (directory)) + Directory.CreateDirectory (directory); + + MonoAndroidHelper.SetWriteable (destination); + File.WriteAllBytes (destination, bytes); + return true; + } + return false; + } + + public static bool CopyIfStreamChanged (Stream stream, string destination) + { + if (HasStreamChanged (stream, destination)) { + var directory = Path.GetDirectoryName (destination); + if (!string.IsNullOrEmpty (directory)) + Directory.CreateDirectory (directory); + + MonoAndroidHelper.SetWriteable (destination); + using (var fileStream = File.Create (destination)) { + stream.Position = 0; //HasStreamChanged read to the end + stream.CopyTo (fileStream); + } + return true; + } + return false; + } + public static bool CopyIfZipChanged (Stream source, string destination) { string hash; @@ -162,7 +201,39 @@ public static bool HasFileChanged (string source, string destination) var src_hash = HashFile (source); var dst_hash = HashFile (destination); - // If the hashed don't match, then the file has changed + // If the hashes don't match, then the file has changed + if (src_hash != dst_hash) + return true; + + return false; + } + + public static bool HasStreamChanged (Stream source, string destination) + { + //If destination is missing, that's definitely a change + if (!File.Exists (destination)) + return true; + + var src_hash = HashStream (source); + var dst_hash = HashFile (destination); + + // If the hashes don't match, then the file has changed + if (src_hash != dst_hash) + return true; + + return false; + } + + public static bool HasBytesChanged (byte [] bytes, string destination) + { + //If destination is missing, that's definitely a change + if (!File.Exists (destination)) + return true; + + var src_hash = HashBytes (bytes); + var dst_hash = HashFile (destination); + + // If the hashes don't match, then the file has changed if (src_hash != dst_hash) return true; @@ -271,9 +342,16 @@ public static bool ExtractAll(ZipArchive zip, string destination, Action subclasses, i public void Save (string filename) { - using (var file = new StreamWriter (filename, false, new UTF8Encoding (false))) + using (var file = new StreamWriter (filename, append: false, encoding: new UTF8Encoding (false))) + Save (file); + } + + public void Save (Stream stream) + { + using (var file = new StreamWriter (stream, new UTF8Encoding (false), bufferSize: 1024, leaveOpen: true)) Save (file); } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs index 4a126097ef7..143a868e6f9 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs @@ -386,6 +386,21 @@ public static bool CopyIfChanged (string source, string destination) return Files.CopyIfChanged (source, destination); } + public static bool CopyIfStringChanged (string contents, string destination) + { + return Files.CopyIfStringChanged (contents, destination); + } + + public static bool CopyIfBytesChanged (byte [] bytes, string destination) + { + return Files.CopyIfBytesChanged (bytes, destination); + } + + public static bool CopyIfStreamChanged (Stream source, string destination) + { + return Files.CopyIfStreamChanged (source, destination); + } + public static bool CopyIfZipChanged (Stream source, string destination) { return Files.CopyIfZipChanged (source, destination);