@@ -337,6 +337,89 @@ Error OnDiskOutputFile::initializeStream() {
337337 return Error::success ();
338338}
339339
340+ namespace {
341+ class OpenFileRAII {
342+ static const int InvalidFd = -1 ;
343+
344+ public:
345+ int Fd = InvalidFd;
346+
347+ ~OpenFileRAII () {
348+ if (Fd != InvalidFd)
349+ llvm::sys::Process::SafelyCloseFileDescriptor (Fd);
350+ }
351+ };
352+
353+ enum class FileDifference : uint8_t {
354+ // / The source and destination paths refer to the exact same file.
355+ IdenticalFile,
356+ // / The source and destination paths refer to separate files with identical
357+ // / contents.
358+ SameContents,
359+ // / The source and destination paths refer to separate files with different
360+ // / contents.
361+ DifferentContents
362+ };
363+ } // end anonymous namespace
364+
365+ static Expected<FileDifference>
366+ areFilesDifferent (const llvm::Twine &Source, const llvm::Twine &Destination) {
367+ if (sys::fs::equivalent (Source, Destination))
368+ return FileDifference::IdenticalFile;
369+
370+ OpenFileRAII SourceFile;
371+ sys::fs::file_status SourceStatus;
372+ // If we can't open the source file, fail.
373+ if (std::error_code EC = sys::fs::openFileForRead (Source, SourceFile.Fd ))
374+ return convertToOutputError (Source, EC);
375+
376+ // If we can't stat the source file, fail.
377+ if (std::error_code EC = sys::fs::status (SourceFile.Fd , SourceStatus))
378+ return convertToOutputError (Source, EC);
379+
380+ OpenFileRAII DestFile;
381+ sys::fs::file_status DestStatus;
382+ // If we can't open the destination file, report different.
383+ if (std::error_code Error =
384+ sys::fs::openFileForRead (Destination, DestFile.Fd ))
385+ return FileDifference::DifferentContents;
386+
387+ // If we can't open the destination file, report different.
388+ if (std::error_code Error = sys::fs::status (DestFile.Fd , DestStatus))
389+ return FileDifference::DifferentContents;
390+
391+ // If the files are different sizes, they must be different.
392+ uint64_t Size = SourceStatus.getSize ();
393+ if (Size != DestStatus.getSize ())
394+ return FileDifference::DifferentContents;
395+
396+ // If both files are zero size, they must be the same.
397+ if (Size == 0 )
398+ return FileDifference::SameContents;
399+
400+ // The two files match in size, so we have to compare the bytes to determine
401+ // if they're the same.
402+ std::error_code SourceRegionErr;
403+ sys::fs::mapped_file_region SourceRegion (
404+ sys::fs::convertFDToNativeFile (SourceFile.Fd ),
405+ sys::fs::mapped_file_region::readonly, Size, 0 , SourceRegionErr);
406+ if (SourceRegionErr)
407+ return convertToOutputError (Source, SourceRegionErr);
408+
409+ std::error_code DestRegionErr;
410+ sys::fs::mapped_file_region DestRegion (
411+ sys::fs::convertFDToNativeFile (DestFile.Fd ),
412+ sys::fs::mapped_file_region::readonly, Size, 0 , DestRegionErr);
413+
414+ if (DestRegionErr)
415+ return FileDifference::DifferentContents;
416+
417+ if (memcmp (SourceRegion.const_data (), DestRegion.const_data (), Size) != 0 )
418+ return FileDifference::DifferentContents;
419+
420+ return FileDifference::SameContents;
421+ }
422+
340423Error OnDiskOutputFile::keep () {
341424 // Destroy the streams to flush them.
342425 BufferOS.reset ();
@@ -351,6 +434,25 @@ Error OnDiskOutputFile::keep() {
351434 if (!TempPath)
352435 return Error::success ();
353436
437+ if (Config.getOnlyIfDifferent ()) {
438+ auto Result = areFilesDifferent (*TempPath, OutputPath);
439+ if (!Result)
440+ return Result.takeError ();
441+ switch (*Result) {
442+ case FileDifference::IdenticalFile:
443+ // Do nothing for a self-move.
444+ return Error::success ();
445+
446+ case FileDifference::SameContents:
447+ // Files are identical; remove the source file.
448+ (void ) sys::fs::remove (*TempPath);
449+ return Error::success ();
450+
451+ case FileDifference::DifferentContents:
452+ break ; // Rename the file.
453+ }
454+ }
455+
354456 // Move temporary to the final output path and remove it if that fails.
355457 std::error_code RenameEC = sys::fs::rename (*TempPath, OutputPath);
356458 if (!RenameEC)
0 commit comments