[GN] Don't rewrite files with the same contents
BUG=
Review URL: https://codereview.chromium.org/1656253003
Cr-Original-Commit-Position: refs/heads/master@{#373544}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: f8ea5cceefcedd4a01935d5ac4d2ba71e23ac13e
diff --git a/tools/gn/filesystem_utils.cc b/tools/gn/filesystem_utils.cc
index 5905cdb..2ceca81 100644
--- a/tools/gn/filesystem_utils.cc
+++ b/tools/gn/filesystem_utils.cc
@@ -687,6 +687,86 @@
return toolchain_label.name() + "/";
}
+bool ContentsEqual(const base::FilePath& file_path, const std::string& data) {
+ // Compare file and stream sizes first. Quick and will save us some time if
+ // they are different sizes.
+ int64_t file_size;
+ if (!base::GetFileSize(file_path, &file_size) ||
+ static_cast<size_t>(file_size) != data.size()) {
+ return false;
+ }
+
+ std::string file_data;
+ file_data.resize(file_size);
+ if (!base::ReadFileToString(file_path, &file_data))
+ return false;
+
+ return file_data == data;
+}
+
+bool WriteFileIfChanged(const base::FilePath& file_path,
+ const std::string& data,
+ Err* err) {
+ if (ContentsEqual(file_path, data))
+ return true;
+
+ // Create the directory if necessary.
+ if (!base::CreateDirectory(file_path.DirName())) {
+ if (err) {
+ *err =
+ Err(Location(), "Unable to create directory.",
+ "I was using \"" + FilePathToUTF8(file_path.DirName()) + "\".");
+ }
+ return false;
+ }
+
+ int size = static_cast<int>(data.size());
+ bool write_success = false;
+
+#if defined(OS_WIN)
+ // On Windows, provide a custom implementation of base::WriteFile. Sometimes
+ // the base version fails, especially on the bots. The guess is that Windows
+ // Defender or other antivirus programs still have the file open (after
+ // checking for the read) when the write happens immediately after. This
+ // version opens with FILE_SHARE_READ (normally not what you want when
+ // replacing the entire contents of the file) which lets us continue even if
+ // another program has the file open for reading. See http://crbug.com/468437
+ base::win::ScopedHandle file(::CreateFile(file_path.value().c_str(),
+ GENERIC_WRITE, FILE_SHARE_READ,
+ NULL, CREATE_ALWAYS, 0, NULL));
+ if (file.IsValid()) {
+ DWORD written;
+ BOOL result = ::WriteFile(file.Get(), data.c_str(), size, &written, NULL);
+ if (result) {
+ if (static_cast<int>(written) == size) {
+ write_success = true;
+ } else {
+ // Didn't write all the bytes.
+ LOG(ERROR) << "wrote" << written << " bytes to "
+ << base::UTF16ToUTF8(file_path.value()) << " expected "
+ << size;
+ }
+ } else {
+ // WriteFile failed.
+ PLOG(ERROR) << "writing file " << base::UTF16ToUTF8(file_path.value())
+ << " failed";
+ }
+ } else {
+ PLOG(ERROR) << "CreateFile failed for path "
+ << base::UTF16ToUTF8(file_path.value());
+ }
+#else
+ write_success = base::WriteFile(file_path, data.c_str(), size) == size;
+#endif
+
+ if (!write_success && err) {
+ *err = Err(Location(), "Unable to write file.",
+ "I was writing \"" + FilePathToUTF8(file_path) + "\".");
+ }
+
+ return write_success;
+}
+
SourceDir GetToolchainOutputDir(const Settings* settings) {
return settings->toolchain_output_subdir().AsSourceDir(
settings->build_settings());
diff --git a/tools/gn/filesystem_utils.h b/tools/gn/filesystem_utils.h
index f64c00d..aaa08ba 100644
--- a/tools/gn/filesystem_utils.h
+++ b/tools/gn/filesystem_utils.h
@@ -163,6 +163,18 @@
// go in the root build directory. Otherwise, the result will end in a slash.
std::string GetOutputSubdirName(const Label& toolchain_label, bool is_default);
+// Returns true if the contents of the file and stream given are equal, false
+// otherwise.
+bool ContentsEqual(const base::FilePath& file_path, const std::string& data);
+
+// Writes given stream contents to the given file if it differs from existing
+// file contents. Returns true if new contents was successfully written or
+// existing file contents doesn't need updating, false on write error. |err| is
+// set on write error if not nullptr.
+bool WriteFileIfChanged(const base::FilePath& file_path,
+ const std::string& data,
+ Err* err);
+
// -----------------------------------------------------------------------------
// These functions return the various flavors of output and gen directories.
diff --git a/tools/gn/filesystem_utils_unittest.cc b/tools/gn/filesystem_utils_unittest.cc
index 27ccab2..e44d337 100644
--- a/tools/gn/filesystem_utils_unittest.cc
+++ b/tools/gn/filesystem_utils_unittest.cc
@@ -3,8 +3,11 @@
// found in the LICENSE file.
#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
+#include "base/threading/platform_thread.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "tools/gn/filesystem_utils.h"
@@ -554,6 +557,59 @@
#endif
}
+TEST(FilesystemUtils, ContentsEqual) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ std::string data = "foo";
+
+ base::FilePath file_path = temp_dir.path().AppendASCII("foo.txt");
+ base::WriteFile(file_path, data.c_str(), static_cast<int>(data.size()));
+
+ EXPECT_TRUE(ContentsEqual(file_path, data));
+
+ // Different length and contents.
+ data += "bar";
+ EXPECT_FALSE(ContentsEqual(file_path, data));
+
+ // The same length, different contents.
+ EXPECT_FALSE(ContentsEqual(file_path, "bar"));
+}
+
+TEST(FilesystemUtils, WriteFileIfChanged) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ std::string data = "foo";
+
+ // Write if file doesn't exist. Create also directory.
+ base::FilePath file_path =
+ temp_dir.path().AppendASCII("bar").AppendASCII("foo.txt");
+ EXPECT_TRUE(WriteFileIfChanged(file_path, data, nullptr));
+
+ base::File::Info file_info;
+ ASSERT_TRUE(base::GetFileInfo(file_path, &file_info));
+ base::Time last_modified = file_info.last_modified;
+
+#if defined(OS_MACOSX)
+ // Modification times are in seconds in HFS on Mac.
+ base::TimeDelta sleep_time = base::TimeDelta::FromSeconds(1);
+#else
+ base::TimeDelta sleep_time = base::TimeDelta::FromMilliseconds(1);
+#endif
+ base::PlatformThread::Sleep(sleep_time);
+
+ // Don't write if contents is the same.
+ EXPECT_TRUE(WriteFileIfChanged(file_path, data, nullptr));
+ ASSERT_TRUE(base::GetFileInfo(file_path, &file_info));
+ EXPECT_EQ(last_modified, file_info.last_modified);
+
+ // Write if contents changed.
+ EXPECT_TRUE(WriteFileIfChanged(file_path, "bar", nullptr));
+ ASSERT_TRUE(base::GetFileInfo(file_path, &file_info));
+ EXPECT_NE(last_modified, file_info.last_modified);
+}
+
TEST(FilesystemUtils, GetToolchainDirs) {
BuildSettings build_settings;
build_settings.SetBuildDir(SourceDir("//out/Debug/"));
diff --git a/tools/gn/function_write_file.cc b/tools/gn/function_write_file.cc
index 9159975..feb1004 100644
--- a/tools/gn/function_write_file.cc
+++ b/tools/gn/function_write_file.cc
@@ -19,55 +19,6 @@
namespace functions {
-namespace {
-
-// On Windows, provide a custom implementation of base::WriteFile. Sometimes
-// the base version fails, especially on the bots. The guess is that Windows
-// Defender or other antivirus programs still have the file open (after
-// checking for the read) when the write happens immediately after. This
-// version opens with FILE_SHARE_READ (normally not what you want when
-// replacing the entire contents of the file) which lets us continue even if
-// another program has the file open for reading. See http://crbug.com/468437
-#if defined(OS_WIN)
-int DoWriteFile(const base::FilePath& filename, const char* data, int size) {
- base::win::ScopedHandle file(::CreateFile(
- filename.value().c_str(),
- GENERIC_WRITE,
- FILE_SHARE_READ,
- NULL,
- CREATE_ALWAYS,
- 0,
- NULL));
- if (!file.IsValid()) {
- PLOG(ERROR) << "CreateFile failed for path "
- << base::UTF16ToUTF8(filename.value());
- return -1;
- }
-
- DWORD written;
- BOOL result = ::WriteFile(file.Get(), data, size, &written, NULL);
- if (result && static_cast<int>(written) == size)
- return written;
-
- if (!result) {
- // WriteFile failed.
- PLOG(ERROR) << "writing file " << base::UTF16ToUTF8(filename.value())
- << " failed";
- } else {
- // Didn't write all the bytes.
- LOG(ERROR) << "wrote" << written << " bytes to "
- << base::UTF16ToUTF8(filename.value()) << " expected " << size;
- }
- return -1;
-}
-#else
-int DoWriteFile(const base::FilePath& filename, const char* data, int size) {
- return base::WriteFile(filename, data, size);
-}
-#endif
-
-} // namespace
-
const char kWriteFile[] = "write_file";
const char kWriteFile_HelpShort[] =
"write_file: Write a file to disk.";
@@ -138,30 +89,14 @@
} else {
contents << args[1].ToString(false);
}
- const std::string& new_contents = contents.str();
+
base::FilePath file_path =
scope->settings()->build_settings()->GetFullPath(source_file);
// Make sure we're not replacing the same contents.
- std::string existing_contents;
- if (base::ReadFileToString(file_path, &existing_contents) &&
- existing_contents == new_contents)
- return Value(); // Nothing to do.
+ if (!WriteFileIfChanged(file_path, contents.str(), err))
+ *err = Err(function->function(), err->message(), err->help_text());
- // Write file, creating the directory if necessary.
- if (!base::CreateDirectory(file_path.DirName())) {
- *err = Err(function->function(), "Unable to create directory.",
- "I was using \"" + FilePathToUTF8(file_path.DirName()) + "\".");
- return Value();
- }
-
- int int_size = static_cast<int>(new_contents.size());
- if (DoWriteFile(file_path, new_contents.c_str(), int_size)
- != int_size) {
- *err = Err(function->function(), "Unable to write file.",
- "I was writing \"" + FilePathToUTF8(file_path) + "\".");
- return Value();
- }
return Value();
}
diff --git a/tools/gn/ninja_target_writer.cc b/tools/gn/ninja_target_writer.cc
index 7d9c08a..9f19b2b 100644
--- a/tools/gn/ninja_target_writer.cc
+++ b/tools/gn/ninja_target_writer.cc
@@ -74,9 +74,7 @@
CHECK(0) << "Output type of target not handled.";
}
- std::string contents = file.str();
- base::WriteFile(ninja_file, contents.c_str(),
- static_cast<int>(contents.size()));
+ WriteFileIfChanged(ninja_file, file.str(), nullptr);
}
void NinjaTargetWriter::WriteEscapedSubstitution(SubstitutionType type) {
diff --git a/tools/gn/visual_studio_writer.cc b/tools/gn/visual_studio_writer.cc
index 44a3f11..dec0837 100644
--- a/tools/gn/visual_studio_writer.cc
+++ b/tools/gn/visual_studio_writer.cc
@@ -9,7 +9,6 @@
#include <set>
#include <string>
-#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/string_util.h"
@@ -153,26 +152,6 @@
return base::StringPiece();
}
-bool HasSameContent(std::stringstream& data_1, const base::FilePath& data_2) {
- // Compare file sizes first. Quick and will save us some time if they are
- // different sizes.
- int64_t data_1_len = data_1.tellp();
-
- int64_t data_2_len;
- if (!base::GetFileSize(data_2, &data_2_len) || data_1_len != data_2_len)
- return false;
-
- std::string data_2_data;
- data_2_data.resize(data_2_len);
- if (!base::ReadFileToString(data_2, &data_2_data))
- return false;
-
- std::string data_1_data;
- data_1_data = data_1.str();
-
- return data_1_data == data_2_data;
-}
-
} // namespace
VisualStudioWriter::SolutionEntry::SolutionEntry(const std::string& _name,
@@ -289,32 +268,13 @@
// Only write the content to the file if it's different. That is
// both a performance optimization and more importantly, prevents
// Visual Studio from reloading the projects.
- if (!HasSameContent(vcxproj_string_out, vcxproj_path)) {
- std::string content = vcxproj_string_out.str();
- int size = static_cast<int>(content.size());
- if (base::WriteFile(vcxproj_path, content.c_str(), size) != size) {
- *err = Err(Location(), "Couldn't open " + target->label().name() +
- ".vcxproj for writing");
- return false;
- }
- }
+ if (!WriteFileIfChanged(vcxproj_path, vcxproj_string_out.str(), err))
+ return false;
base::FilePath filters_path = UTF8ToFilePath(vcxproj_path_str + ".filters");
-
std::stringstream filters_string_out;
WriteFiltersFileContents(filters_string_out, target);
-
- if (!HasSameContent(filters_string_out, filters_path)) {
- std::string content = filters_string_out.str();
- int size = static_cast<int>(content.size());
- if (base::WriteFile(filters_path, content.c_str(), size) != size) {
- *err = Err(Location(), "Couldn't open " + target->label().name() +
- ".vcxproj.filters for writing");
- return false;
- }
- }
-
- return true;
+ return WriteFileIfChanged(filters_path, filters_string_out.str(), err);
}
bool VisualStudioWriter::WriteProjectFileContents(
@@ -612,17 +572,7 @@
// Only write the content to the file if it's different. That is
// both a performance optimization and more importantly, prevents
// Visual Studio from reloading the projects.
- if (HasSameContent(string_out, sln_path))
- return true;
-
- std::string content = string_out.str();
- int size = static_cast<int>(content.size());
- if (base::WriteFile(sln_path, content.c_str(), size) != size) {
- *err = Err(Location(), "Couldn't open all.sln for writing");
- return false;
- }
-
- return true;
+ return WriteFileIfChanged(sln_path, string_out.str(), err);
}
void VisualStudioWriter::WriteSolutionFileContents(