diff --git a/Source/MSBuild.Community.Tasks.Tests/AssemblyInfoTest.cs b/Source/MSBuild.Community.Tasks.Tests/AssemblyInfoTest.cs index 0bc6ffaf..0fd8c1c3 100644 --- a/Source/MSBuild.Community.Tasks.Tests/AssemblyInfoTest.cs +++ b/Source/MSBuild.Community.Tasks.Tests/AssemblyInfoTest.cs @@ -184,6 +184,32 @@ public void IncludeAllowPartiallyTrustedCallers() Assert.That(content.Contains("assembly: System.Security.AllowPartiallyTrustedCallers()")); } + [Test(Description = "Create VersionInfo twice with OnlyIfChanged set and make sure it wasn't recreated the second time")] + public void AssemblyInfoNotRecreatedIfUnchanged() + { + AssemblyInfo task = new AssemblyInfo(); + task.BuildEngine = new MockBuild(); + task.CodeLanguage = "cs"; + string outputFile = Path.Combine(testDirectory, "VersionInfoTwice.cs"); + task.OutputFile = outputFile; + task.AssemblyVersion = "1.2.3.4"; + task.AssemblyFileVersion = "1.2.3.4"; + task.AssemblyInformationalVersion = "1.2.3.4"; + task.GenerateClass = true; + task.OnlyIfChanged = true; + + Assert.IsTrue(task.Execute(), "Execute Failed"); + Assert.IsTrue(File.Exists(outputFile), "File missing: " + outputFile); + + var time = File.GetLastWriteTime(outputFile); + + // Create again, with OnlyIfChanged set it should not be recreated + Assert.IsTrue(task.Execute(), "Second execute failed"); + + var secondTime = File.GetLastWriteTime(outputFile); + Assert.AreEqual(time, secondTime, "File was recreated although it shouldn't be"); + } + private AssemblyInfo CreateCSAssemblyInfo(string outputFile) { AssemblyInfo task = new AssemblyInfo(); diff --git a/Source/MSBuild.Community.Tasks/AssemblyInfo.cs b/Source/MSBuild.Community.Tasks/AssemblyInfo.cs index 18731864..841fe07e 100644 --- a/Source/MSBuild.Community.Tasks/AssemblyInfo.cs +++ b/Source/MSBuild.Community.Tasks/AssemblyInfo.cs @@ -436,6 +436,11 @@ public bool UnmanagedCode /// The ultimate resource fallback location. public string UltimateResourceFallbackLocation { get; set; } + /// + /// Gets or sets a value indicating whether the file should be generated only if different from any already existing file + /// + public bool OnlyIfChanged { get; set; } + /// /// Makes it possible to make certain assemblies able to use constructs marked as internal. /// Example might be setting this value to "UnitTests" assembly. The typical use case might @@ -491,12 +496,41 @@ public override bool Execute() Encoding utf8WithSignature = new UTF8Encoding(true); - using (StreamWriter writer = new StreamWriter(_outputFile, false, utf8WithSignature)) + if (OnlyIfChanged && File.Exists(_outputFile)) { - GenerateFile(writer); - writer.Flush(); - writer.Close(); - Log.LogMessage("Created AssemblyInfo file \"{0}\".", _outputFile); + // The file already exists. If we generate an identical file, don't overwrite the existing one + using (var memoryStream = new MemoryStream()) + { + using (StreamWriter writer = new StreamWriter(memoryStream, utf8WithSignature)) + { + GenerateFile(writer); + writer.Flush(); + + using (var existingFile = File.OpenRead(_outputFile)) + { + memoryStream.Seek(0, SeekOrigin.Begin); + + if (StreamTools.StreamEquals(existingFile, memoryStream)) + { + Log.LogMessage("Identical AssemblyInfo file \"{0}\" already exists.", _outputFile); + return true; + } + } + + File.WriteAllBytes(_outputFile, memoryStream.ToArray()); + Log.LogMessage("Created updated AssemblyInfo file \"{0}\".", _outputFile); + } + } + } + else + { + using (StreamWriter writer = new StreamWriter(_outputFile, false, utf8WithSignature)) + { + GenerateFile(writer); + writer.Flush(); + writer.Close(); + Log.LogMessage("Created AssemblyInfo file \"{0}\".", _outputFile); + } } return true; diff --git a/Source/MSBuild.Community.Tasks/MSBuild.Community.Tasks.csproj b/Source/MSBuild.Community.Tasks/MSBuild.Community.Tasks.csproj index f4a16a13..1083567e 100644 --- a/Source/MSBuild.Community.Tasks/MSBuild.Community.Tasks.csproj +++ b/Source/MSBuild.Community.Tasks/MSBuild.Community.Tasks.csproj @@ -172,6 +172,7 @@ + diff --git a/Source/MSBuild.Community.Tasks/StreamTools.cs b/Source/MSBuild.Community.Tasks/StreamTools.cs new file mode 100644 index 00000000..47f8cf20 --- /dev/null +++ b/Source/MSBuild.Community.Tasks/StreamTools.cs @@ -0,0 +1,30 @@ +using System.IO; +using System.Linq; + +namespace MSBuild.Community.Tasks +{ + internal static class StreamTools + { + public static bool StreamEquals(Stream stream1, Stream stream2) + { + const int bufferSize = 2048; + byte[] buffer1 = new byte[bufferSize]; //buffer size + byte[] buffer2 = new byte[bufferSize]; + while (true) + { + int count1 = stream1.Read(buffer1, 0, bufferSize); + int count2 = stream2.Read(buffer2, 0, bufferSize); + + if (count1 != count2) + return false; + + if (count1 == 0) + return true; + + // You might replace the following with an efficient "memcmp" + if (!buffer1.Take(count1).SequenceEqual(buffer2.Take(count2))) + return false; + } + } + } +}