Fix WriteLinesToFile rewriting unchanged file when custom encoding is used#14146
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes WriteLinesToFile’s WriteOnlyWhenDifferent="true" behavior when a custom Encoding is specified, ensuring the task correctly detects unchanged output and avoids rewriting the file (preserving timestamps and incremental build correctness).
Changes:
- Thread the resolved
EncodingthroughExecute→ShouldWriteFileForOverwrite→FilesAreIdentical. - Update
FilesAreIdenticalto include the encoding preamble (BOM) in both length and byte-by-byte comparisons. - Add a regression test covering multiple BOM-emitting encodings.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/Tasks/FileIO/WriteLinesToFile.cs | Use the actual task encoding (including BOM) when determining whether an overwrite would change file bytes. |
| src/Tasks.UnitTests/WriteLinesToFile_Tests.cs | Add regression coverage for WriteOnlyWhenDifferent with custom encodings. |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
/review |
…ing-writeonlywhendifferent
…ing-writeonlywhendifferent
Expert review (ultimate-reviewer · portable core + dotnet/msbuild expert-reviewer)Verdict: LGTM — 0 blocking, 0 major, 0 moderate, 1 optional nit. 12 of 13 evaluated dimensions clean. Considered the prior conversation: the 3 earlier Copilot inline concerns are all resolved by the simplification in Prior review concerns — all resolved
Why the fix is correct (verified)
Compatibility / ChangeWaveNo gate needed. This restores the documented Clean dimensionsCorrectness/edge cases, Backward-compat/ChangeWave, Concurrency ( Optional nit — test coverage
Automated review via the ultimate-reviewer skill. Findings are advisory. |
Fixes #14071
Context
WriteLinesToFilewithWriteOnlyWhenDifferent="true"is supposed to skip the write (and preserve the file timestamp) when the content it would produce is identical to what's already on disk. This keeps incremental builds fast and avoids unnecessary timestamp churn that triggers downstream rebuilds.This skip logic was broken whenever a custom
Encodingwas specified (utf-8,unicode,utf-32, etc.): the file was rewritten on every build even when nothing changed, so any target depending on its timestamp re-ran each time.There were two compounding root causes in
FilesAreIdentical:s_defaultEncoding) instead of the encoding the task actually writes the file with.File.WriteAllTextprependsencoding.GetPreamble()(the BOM) to the file, but the comparison never accounted for those bytes.Because of (1) and (2), the byte length and/or content never matched the on-disk file, so it was always treated as "different" and rewritten.
Changes Made
encodingthrough the call chain:Execute→ShouldWriteFileForOverwrite→FilesAreIdentical.FilesAreIdentical, encode the candidate content with thatencodingand includeencoding.GetPreamble()in both the length check and the byte-by-byte comparison.s_defaultEncodingis still used as the default when noEncodingis supplied — no dead code.Testing
Notes