diff --git a/src/Build.UnitTests/BinaryLogger_Tests.cs b/src/Build.UnitTests/BinaryLogger_Tests.cs index 82d9c9194f4..e47fb19b1e2 100644 --- a/src/Build.UnitTests/BinaryLogger_Tests.cs +++ b/src/Build.UnitTests/BinaryLogger_Tests.cs @@ -7,6 +7,7 @@ using System.IO.Compression; using System.Linq; using System.Text; +using System.Threading; using FakeItEasy; using FluentAssertions; using Microsoft.Build.BackEnd.Logging; @@ -967,6 +968,122 @@ public void ProcessParameters_MixedConfigsWithDuplicates_HandledCorrectly() #endregion + #region Forward Compatibility Replay Tests + // These tests exercise in-memory streams rather than .binlog files, + // but the fixture's _logFile must exist at Dispose time. + + [Fact] + public void OpenBuildEventsReader_ThrowsForIncompatibleVersion() + { + // fileFormatVersion > current AND minimumReaderVersion > current => fatal + var stream = new MemoryStream(); + using var writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true); + writer.Write(BinaryLogger.FileFormatVersion + 10); // fileFormatVersion + writer.Write(BinaryLogger.FileFormatVersion + 5); // minimumReaderVersion (too high) + writer.Flush(); + stream.Position = 0; + + using var reader = new BinaryReader(stream); + Should.Throw(() => + BinaryLogReplayEventSource.OpenBuildEventsReader(reader, closeInput: false, allowForwardCompatibility: true)); + + CreateExpectedLogFile(); + } + + [Fact] + public void OpenBuildEventsReader_SucceedsForForwardCompatibleVersion() + { + // fileFormatVersion > current but minimumReaderVersion <= current => should succeed + var stream = new MemoryStream(); + using var writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true); + writer.Write(BinaryLogger.FileFormatVersion + 5); // fileFormatVersion (newer) + writer.Write(BinaryLogger.ForwardCompatibilityMinimalVersion); // minimumReaderVersion (compatible) + writer.Flush(); + stream.Position = 0; + + using var reader = new BinaryReader(stream); + using var eventsReader = BinaryLogReplayEventSource.OpenBuildEventsReader(reader, closeInput: false, allowForwardCompatibility: true); + eventsReader.ShouldNotBeNull(); + eventsReader.FileFormatVersion.ShouldBe(BinaryLogger.FileFormatVersion + 5); + + CreateExpectedLogFile(); + } + + [Fact] + public void OpenBuildEventsReader_ThrowsWithoutForwardCompatibility() + { + // fileFormatVersion > current, allowForwardCompatibility = false => fatal even if minimumReaderVersion is ok + var stream = new MemoryStream(); + using var writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true); + writer.Write(BinaryLogger.FileFormatVersion + 5); // fileFormatVersion (newer) + writer.Write(BinaryLogger.ForwardCompatibilityMinimalVersion); // minimumReaderVersion (compatible) + writer.Flush(); + stream.Position = 0; + + using var reader = new BinaryReader(stream); + Should.Throw(() => + BinaryLogReplayEventSource.OpenBuildEventsReader(reader, closeInput: false, allowForwardCompatibility: false)); + + CreateExpectedLogFile(); + } + + /// + /// Creates an empty placeholder so the fixture's Dispose doesn't fail + /// when the test didn't produce a real .binlog file. + /// + private void CreateExpectedLogFile() => File.Create(_logFile).Dispose(); + + [Fact] + public void FormatVersionMismatchWarning_NullForCurrentVersion() + { + _env.SetEnvironmentVariable("MSBUILDTARGETOUTPUTLOGGING", "1"); + + var binaryLogger = new BinaryLogger { Parameters = _logFile }; + + using (ProjectCollection collection = new()) + { + Project project = ObjectModelHelpers.CreateInMemoryProject(collection, s_testProject); + project.Build(new ILogger[] { binaryLogger }).ShouldBeTrue(); + } + + var replayEventSource = new BinaryLogReplayEventSource(); + replayEventSource.RecoverableReadError += _ => { }; + replayEventSource.BuildFinished += (_, _) => { }; + replayEventSource.Replay(_logFile); + + replayEventSource.FormatVersionMismatchWarning.ShouldBeNull(); + } + + [Fact] + public void FormatVersionMismatchWarning_NonNullForNewerVersion() + { + int newerVersion = BinaryLogger.FileFormatVersion + 5; + + var stream = new MemoryStream(); + using var writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true); + writer.Write(newerVersion); // fileFormatVersion + writer.Write(BinaryLogger.ForwardCompatibilityMinimalVersion); // minimumReaderVersion + stream.WriteByte(0); // EndOfFile record + writer.Flush(); + stream.Position = 0; + + using var binaryReader = new BinaryReader(stream); + using var eventsReader = BinaryLogReplayEventSource.OpenBuildEventsReader(binaryReader, closeInput: false, allowForwardCompatibility: true); + + var replayEventSource = new BinaryLogReplayEventSource(); + replayEventSource.RecoverableReadError += _ => { }; + replayEventSource.BuildFinished += (_, _) => { }; + replayEventSource.Replay(eventsReader, CancellationToken.None); + + replayEventSource.FormatVersionMismatchWarning.ShouldNotBeNull(); + replayEventSource.FormatVersionMismatchWarning.ShouldContain(newerVersion.ToString()); + replayEventSource.FormatVersionMismatchWarning.ShouldContain(BinaryLogger.FileFormatVersion.ToString()); + + CreateExpectedLogFile(); + } + + #endregion + public void Dispose() { _env.Dispose(); diff --git a/src/Build/Logging/BinaryLogger/BinaryLogReplayEventSource.cs b/src/Build/Logging/BinaryLogger/BinaryLogReplayEventSource.cs index 1e7ab846695..a5f000fddd9 100644 --- a/src/Build/Logging/BinaryLogger/BinaryLogReplayEventSource.cs +++ b/src/Build/Logging/BinaryLogger/BinaryLogReplayEventSource.cs @@ -65,10 +65,17 @@ public sealed class BinaryLogReplayEventSource : { private int? _fileFormatVersion; private int? _minimumReaderVersion; + private string? _formatVersionMismatchWarning; public int FileFormatVersion => _fileFormatVersion ?? throw new InvalidOperationException(ResourceUtilities.GetResourceString("Binlog_Source_VersionUninitialized")); public int MinimumReaderVersion => _minimumReaderVersion ?? throw new InvalidOperationException(ResourceUtilities.GetResourceString("Binlog_Source_VersionUninitialized")); + /// + /// After replay, contains a warning message if the binlog was produced by a newer version of MSBuild. + /// Returns if has not been called yet or if the versions match. + /// + public string? FormatVersionMismatchWarning => _formatVersionMismatchWarning; + /// Touches the static constructor /// to ensure it initializes /// and @@ -197,7 +204,7 @@ public static BuildEventArgsReader OpenBuildEventsReader(string sourceFilePath) /// A indicating the replay should stop as soon as possible. public void Replay(string sourceFilePath, CancellationToken cancellationToken) { - using var eventsReader = OpenBuildEventsReader(sourceFilePath); + using var eventsReader = OpenBuildEventsReader(OpenReader(sourceFilePath), true, AllowForwardCompatibility); Replay(eventsReader, cancellationToken); } @@ -230,6 +237,9 @@ public void Replay(BuildEventArgsReader reader, CancellationToken cancellationTo { _fileFormatVersion = reader.FileFormatVersion; _minimumReaderVersion = reader.MinimumReaderVersion; + _formatVersionMismatchWarning = reader.FileFormatVersion > BinaryLogger.FileFormatVersion + ? ResourceUtilities.FormatResourceStringStripCodeAndKeyword("BinlogFormatVersionMismatch", reader.FileFormatVersion, BinaryLogger.FileFormatVersion) + : null; bool supportsForwardCompatibility = reader.FileFormatVersion >= BinaryLogger.ForwardCompatibilityMinimalVersion; // Allow any possible deferred subscriptions to be registered @@ -254,9 +264,12 @@ public void Replay(BuildEventArgsReader reader, CancellationToken cancellationTo ResourceUtilities.GetResourceString("Binlog_Source_MultiSubscribeError")); } - // Forward compatible reading makes sense only for structured events reading. - reader.SkipUnknownEvents = supportsForwardCompatibility && AllowForwardCompatibility; - reader.SkipUnknownEventParts = supportsForwardCompatibility && AllowForwardCompatibility; + // Forward compatible reading makes sense only for structured events reading + // and only when the binlog is actually from a newer version. + bool skipUnknown = supportsForwardCompatibility && AllowForwardCompatibility + && reader.FileFormatVersion > BinaryLogger.FileFormatVersion; + reader.SkipUnknownEvents = skipUnknown; + reader.SkipUnknownEventParts = skipUnknown; reader.RecoverableReadError += RecoverableReadError; while (!cancellationToken.IsCancellationRequested && reader.Read() is { } instance) diff --git a/src/Build/Resources/Strings.resx b/src/Build/Resources/Strings.resx index b4bed36c6bd..b63e28e1a7f 100644 --- a/src/Build/Resources/Strings.resx +++ b/src/Build/Resources/Strings.resx @@ -2135,6 +2135,10 @@ Utilization: {0} Average Utilization: {1:###.0} LOCALIZATION: {0} is integer number denoting number of bytes. 'int.MaxValue' should not be translated. + + MSB4281: The binary log file was created with a newer version of MSBuild (format version {0}) than the current version (format version {1}). Some data may be missing from the replay. + {StrBegin="MSB4281: "}{0} is the binlog file format version. {1} is the current MSBuild file format version. + Custom check rule: '{0}' has been registered successfully. The message is emitted on successful loading of the custom check rule in process. @@ -2477,7 +2481,7 @@ Utilization: {0} Average Utilization: {1:###.0} diff --git a/src/Build/Resources/xlf/Strings.cs.xlf b/src/Build/Resources/xlf/Strings.cs.xlf index e25a9d8bbf5..d477592535b 100644 --- a/src/Build/Resources/xlf/Strings.cs.xlf +++ b/src/Build/Resources/xlf/Strings.cs.xlf @@ -36,6 +36,11 @@ {StrBegin="MSB4003: "}UE: Tasks are not allowed to use incorrect case for reserved attributes on the task nodes e.g. "continueonerror" instead of the "ContinueOnError". + + MSB4281: The binary log file was created with a newer version of MSBuild (format version {0}) than the current version (format version {1}). Some data may be missing from the replay. + MSB4281: The binary log file was created with a newer version of MSBuild (format version {0}) than the current version (format version {1}). Some data may be missing from the replay. + {StrBegin="MSB4281: "}{0} is the binlog file format version. {1} is the current MSBuild file format version. + Content already acquired as StreamReader via GetContentReader. Obsah se už získal jako StreamReader přes GetContentReader. diff --git a/src/Build/Resources/xlf/Strings.de.xlf b/src/Build/Resources/xlf/Strings.de.xlf index 7ed77836b21..b077163c95f 100644 --- a/src/Build/Resources/xlf/Strings.de.xlf +++ b/src/Build/Resources/xlf/Strings.de.xlf @@ -36,6 +36,11 @@ {StrBegin="MSB4003: "}UE: Tasks are not allowed to use incorrect case for reserved attributes on the task nodes e.g. "continueonerror" instead of the "ContinueOnError". + + MSB4281: The binary log file was created with a newer version of MSBuild (format version {0}) than the current version (format version {1}). Some data may be missing from the replay. + MSB4281: The binary log file was created with a newer version of MSBuild (format version {0}) than the current version (format version {1}). Some data may be missing from the replay. + {StrBegin="MSB4281: "}{0} is the binlog file format version. {1} is the current MSBuild file format version. + Content already acquired as StreamReader via GetContentReader. Inhalt, der bereits als „StreamReader“ über „GetContentReader“ abgerufen wurde. diff --git a/src/Build/Resources/xlf/Strings.es.xlf b/src/Build/Resources/xlf/Strings.es.xlf index a6bf552039f..55684896818 100644 --- a/src/Build/Resources/xlf/Strings.es.xlf +++ b/src/Build/Resources/xlf/Strings.es.xlf @@ -36,6 +36,11 @@ {StrBegin="MSB4003: "}UE: Tasks are not allowed to use incorrect case for reserved attributes on the task nodes e.g. "continueonerror" instead of the "ContinueOnError". + + MSB4281: The binary log file was created with a newer version of MSBuild (format version {0}) than the current version (format version {1}). Some data may be missing from the replay. + MSB4281: The binary log file was created with a newer version of MSBuild (format version {0}) than the current version (format version {1}). Some data may be missing from the replay. + {StrBegin="MSB4281: "}{0} is the binlog file format version. {1} is the current MSBuild file format version. + Content already acquired as StreamReader via GetContentReader. Contenido ya adquirido como StreamReader a través de GetContentReader. diff --git a/src/Build/Resources/xlf/Strings.fr.xlf b/src/Build/Resources/xlf/Strings.fr.xlf index 74c62f3058f..5a75745c25b 100644 --- a/src/Build/Resources/xlf/Strings.fr.xlf +++ b/src/Build/Resources/xlf/Strings.fr.xlf @@ -36,6 +36,11 @@ {StrBegin="MSB4003: "}UE: Tasks are not allowed to use incorrect case for reserved attributes on the task nodes e.g. "continueonerror" instead of the "ContinueOnError". + + MSB4281: The binary log file was created with a newer version of MSBuild (format version {0}) than the current version (format version {1}). Some data may be missing from the replay. + MSB4281: The binary log file was created with a newer version of MSBuild (format version {0}) than the current version (format version {1}). Some data may be missing from the replay. + {StrBegin="MSB4281: "}{0} is the binlog file format version. {1} is the current MSBuild file format version. + Content already acquired as StreamReader via GetContentReader. Contenu déjà acquis en tant que StreamReader par le biais de GetContentReader. diff --git a/src/Build/Resources/xlf/Strings.it.xlf b/src/Build/Resources/xlf/Strings.it.xlf index a1bfcddfc5a..2b5d85c0ef0 100644 --- a/src/Build/Resources/xlf/Strings.it.xlf +++ b/src/Build/Resources/xlf/Strings.it.xlf @@ -36,6 +36,11 @@ {StrBegin="MSB4003: "}UE: Tasks are not allowed to use incorrect case for reserved attributes on the task nodes e.g. "continueonerror" instead of the "ContinueOnError". + + MSB4281: The binary log file was created with a newer version of MSBuild (format version {0}) than the current version (format version {1}). Some data may be missing from the replay. + MSB4281: The binary log file was created with a newer version of MSBuild (format version {0}) than the current version (format version {1}). Some data may be missing from the replay. + {StrBegin="MSB4281: "}{0} is the binlog file format version. {1} is the current MSBuild file format version. + Content already acquired as StreamReader via GetContentReader. Contenuto già acquisito come StreamReader tramite GetContentReader. diff --git a/src/Build/Resources/xlf/Strings.ja.xlf b/src/Build/Resources/xlf/Strings.ja.xlf index 5daeeb25828..f493adcfa47 100644 --- a/src/Build/Resources/xlf/Strings.ja.xlf +++ b/src/Build/Resources/xlf/Strings.ja.xlf @@ -36,6 +36,11 @@ {StrBegin="MSB4003: "}UE: Tasks are not allowed to use incorrect case for reserved attributes on the task nodes e.g. "continueonerror" instead of the "ContinueOnError". + + MSB4281: The binary log file was created with a newer version of MSBuild (format version {0}) than the current version (format version {1}). Some data may be missing from the replay. + MSB4281: The binary log file was created with a newer version of MSBuild (format version {0}) than the current version (format version {1}). Some data may be missing from the replay. + {StrBegin="MSB4281: "}{0} is the binlog file format version. {1} is the current MSBuild file format version. + Content already acquired as StreamReader via GetContentReader. GetContentReader を介して StreamReader として既に取得されたコンテンツ。 diff --git a/src/Build/Resources/xlf/Strings.ko.xlf b/src/Build/Resources/xlf/Strings.ko.xlf index ca31d528a30..0196db61439 100644 --- a/src/Build/Resources/xlf/Strings.ko.xlf +++ b/src/Build/Resources/xlf/Strings.ko.xlf @@ -36,6 +36,11 @@ {StrBegin="MSB4003: "}UE: Tasks are not allowed to use incorrect case for reserved attributes on the task nodes e.g. "continueonerror" instead of the "ContinueOnError". + + MSB4281: The binary log file was created with a newer version of MSBuild (format version {0}) than the current version (format version {1}). Some data may be missing from the replay. + MSB4281: The binary log file was created with a newer version of MSBuild (format version {0}) than the current version (format version {1}). Some data may be missing from the replay. + {StrBegin="MSB4281: "}{0} is the binlog file format version. {1} is the current MSBuild file format version. + Content already acquired as StreamReader via GetContentReader. GetContentReader를 통해 이미 StreamReader로 획득한 콘텐츠입니다. diff --git a/src/Build/Resources/xlf/Strings.pl.xlf b/src/Build/Resources/xlf/Strings.pl.xlf index 3ef14187401..62a054e5ee6 100644 --- a/src/Build/Resources/xlf/Strings.pl.xlf +++ b/src/Build/Resources/xlf/Strings.pl.xlf @@ -36,6 +36,11 @@ {StrBegin="MSB4003: "}UE: Tasks are not allowed to use incorrect case for reserved attributes on the task nodes e.g. "continueonerror" instead of the "ContinueOnError". + + MSB4281: The binary log file was created with a newer version of MSBuild (format version {0}) than the current version (format version {1}). Some data may be missing from the replay. + MSB4281: The binary log file was created with a newer version of MSBuild (format version {0}) than the current version (format version {1}). Some data may be missing from the replay. + {StrBegin="MSB4281: "}{0} is the binlog file format version. {1} is the current MSBuild file format version. + Content already acquired as StreamReader via GetContentReader. Zawartość została już pobrana jako element StreamReader za pośrednictwem metody GetContentReader. diff --git a/src/Build/Resources/xlf/Strings.pt-BR.xlf b/src/Build/Resources/xlf/Strings.pt-BR.xlf index 6334095e5b6..e5cc39449cf 100644 --- a/src/Build/Resources/xlf/Strings.pt-BR.xlf +++ b/src/Build/Resources/xlf/Strings.pt-BR.xlf @@ -36,6 +36,11 @@ {StrBegin="MSB4003: "}UE: Tasks are not allowed to use incorrect case for reserved attributes on the task nodes e.g. "continueonerror" instead of the "ContinueOnError". + + MSB4281: The binary log file was created with a newer version of MSBuild (format version {0}) than the current version (format version {1}). Some data may be missing from the replay. + MSB4281: The binary log file was created with a newer version of MSBuild (format version {0}) than the current version (format version {1}). Some data may be missing from the replay. + {StrBegin="MSB4281: "}{0} is the binlog file format version. {1} is the current MSBuild file format version. + Content already acquired as StreamReader via GetContentReader. Conteúdo já adquirido como StreamReader por meio de GetContentReader. diff --git a/src/Build/Resources/xlf/Strings.ru.xlf b/src/Build/Resources/xlf/Strings.ru.xlf index 0dbdc581f49..656a412dfee 100644 --- a/src/Build/Resources/xlf/Strings.ru.xlf +++ b/src/Build/Resources/xlf/Strings.ru.xlf @@ -36,6 +36,11 @@ {StrBegin="MSB4003: "}UE: Tasks are not allowed to use incorrect case for reserved attributes on the task nodes e.g. "continueonerror" instead of the "ContinueOnError". + + MSB4281: The binary log file was created with a newer version of MSBuild (format version {0}) than the current version (format version {1}). Some data may be missing from the replay. + MSB4281: The binary log file was created with a newer version of MSBuild (format version {0}) than the current version (format version {1}). Some data may be missing from the replay. + {StrBegin="MSB4281: "}{0} is the binlog file format version. {1} is the current MSBuild file format version. + Content already acquired as StreamReader via GetContentReader. Содержимое, уже получено как StreamReader через GetContentReader. diff --git a/src/Build/Resources/xlf/Strings.tr.xlf b/src/Build/Resources/xlf/Strings.tr.xlf index 11758519b4c..b7a6b30f8ec 100644 --- a/src/Build/Resources/xlf/Strings.tr.xlf +++ b/src/Build/Resources/xlf/Strings.tr.xlf @@ -36,6 +36,11 @@ {StrBegin="MSB4003: "}UE: Tasks are not allowed to use incorrect case for reserved attributes on the task nodes e.g. "continueonerror" instead of the "ContinueOnError". + + MSB4281: The binary log file was created with a newer version of MSBuild (format version {0}) than the current version (format version {1}). Some data may be missing from the replay. + MSB4281: The binary log file was created with a newer version of MSBuild (format version {0}) than the current version (format version {1}). Some data may be missing from the replay. + {StrBegin="MSB4281: "}{0} is the binlog file format version. {1} is the current MSBuild file format version. + Content already acquired as StreamReader via GetContentReader. İçerik zaten GetContentReader ara StreamReader olarak alındı. diff --git a/src/Build/Resources/xlf/Strings.zh-Hans.xlf b/src/Build/Resources/xlf/Strings.zh-Hans.xlf index 6ca07df43f7..58c01e2ed66 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hans.xlf @@ -36,6 +36,11 @@ {StrBegin="MSB4003: "}UE: Tasks are not allowed to use incorrect case for reserved attributes on the task nodes e.g. "continueonerror" instead of the "ContinueOnError". + + MSB4281: The binary log file was created with a newer version of MSBuild (format version {0}) than the current version (format version {1}). Some data may be missing from the replay. + MSB4281: The binary log file was created with a newer version of MSBuild (format version {0}) than the current version (format version {1}). Some data may be missing from the replay. + {StrBegin="MSB4281: "}{0} is the binlog file format version. {1} is the current MSBuild file format version. + Content already acquired as StreamReader via GetContentReader. 已通过 GetContentReader 以 StreamReader 形式获取的内容。 diff --git a/src/Build/Resources/xlf/Strings.zh-Hant.xlf b/src/Build/Resources/xlf/Strings.zh-Hant.xlf index 6f8a4402894..519dfb332d7 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hant.xlf @@ -36,6 +36,11 @@ {StrBegin="MSB4003: "}UE: Tasks are not allowed to use incorrect case for reserved attributes on the task nodes e.g. "continueonerror" instead of the "ContinueOnError". + + MSB4281: The binary log file was created with a newer version of MSBuild (format version {0}) than the current version (format version {1}). Some data may be missing from the replay. + MSB4281: The binary log file was created with a newer version of MSBuild (format version {0}) than the current version (format version {1}). Some data may be missing from the replay. + {StrBegin="MSB4281: "}{0} is the binlog file format version. {1} is the current MSBuild file format version. + Content already acquired as StreamReader via GetContentReader. 內容已透過 GetContentReader 取得為 StreamReader。 diff --git a/src/MSBuild/XMake.cs b/src/MSBuild/XMake.cs index 60720e1d842..b14b0d45b29 100644 --- a/src/MSBuild/XMake.cs +++ b/src/MSBuild/XMake.cs @@ -4046,7 +4046,12 @@ private static void ReplayBinaryLog( bool isBuildCheckEnabled) { - var replayEventSource = new BinaryLogReplayEventSource(); + var replayEventSource = new BinaryLogReplayEventSource() { AllowForwardCompatibility = true }; + + // Required when AllowForwardCompatibility is true — the reader requires a subscriber + // for recoverable errors when skipping unknown events from newer-version binlogs. + // For same-version binlogs the skip flags are not set, so this handler never fires. + replayEventSource.RecoverableReadError += _ => { }; var eventSource = isBuildCheckEnabled ? BuildCheckReplayModeConnector.GetMergedEventSource(BuildManager.DefaultBuildManager, replayEventSource) : @@ -4080,6 +4085,11 @@ private static void ReplayBinaryLog( try { replayEventSource.Replay(binaryLogFilePath, s_buildCancellationSource.Token); + + if (replayEventSource.FormatVersionMismatchWarning is string warning) + { + Console.WriteLine(warning); + } } catch (Exception ex) {