Restat build.ninja after gen
As of version 1.8, ninja caches mtimes in the .ninja_log file and will
not restat a file if it has an entry in the .ninja_log. This becomes
problematic for generator files such as build.ninja. In a scenario where
the .ninja_log has an entry for build.ninja (ie, after ninja has had to
trigger a re-gen) and a manual run of of `gn gen` has updated the
build.ninja file, a subsequent invocation of ninja will trigger yet
another re-gen of build.ninja.
This can be reproduced like this:
# Initial build
gn gen out && ninja -C out
# Trigger ninja re-gen so there's a build.ninja log entry
touch BUILD.gn && ninja -C out
# Manual re-gen
gn gen out
# The following command will re-gen again before building
ninja -C out
To work around this, the recommended approach from ninja is to use the
ninja restat tool to get ninja to update the mtime in the .ninja_log.
This CL does that by attempting to invoke `ninja -t restat build.ninja`
immediately after the build.ninja is generated by `gn gen`. Because this
tool was only introduced in 1.10, the usage is gated on having a ninja
binary of at least that version. The ninja binary can be provided via
the --ninja-executable switch.
Bug: 136
Change-Id: If40391aa13e6ef71c4e8ab26aff57d66a8137b8e
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/10400
Commit-Queue: RJ Ascani <rjascani@google.com>
Reviewed-by: Brett Wilson <brettw@chromium.org>
diff --git a/build/gen.py b/build/gen.py
index 486354b..0f40b75 100755
--- a/build/gen.py
+++ b/build/gen.py
@@ -586,6 +586,7 @@
'src/gn/ninja_target_command_util.cc',
'src/gn/ninja_target_writer.cc',
'src/gn/ninja_toolchain_writer.cc',
+ 'src/gn/ninja_tools.cc',
'src/gn/ninja_utils.cc',
'src/gn/ninja_writer.cc',
'src/gn/operators.cc',
diff --git a/docs/reference.md b/docs/reference.md
index 06ab5a0..8eb1510 100644
--- a/docs/reference.md
+++ b/docs/reference.md
@@ -718,6 +718,16 @@
See "gn help switches" for the common command-line switches.
```
+#### **General options**
+
+```
+ --ninja-executable=<string>
+ Can be used to specify the ninja executable to use. This executable will
+ be used as an IDE option to indicate which ninja to use for building. This
+ executable will also be used as part of the gen process for triggering a
+ restat on generated ninja files.
+```
+
#### **IDE options**
```
@@ -7760,6 +7770,7 @@
* --dotfile: Override the name of the ".gn" file.
* --fail-on-unused-args: Treat unused build args as fatal errors.
* --markdown: Write help output in the Markdown format.
+ * --ninja-executable: Set the Ninja executable.
* --nocolor: Force non-colored output.
* -q: Quiet mode. Don't print output on success.
* --root: Explicitly specify source root.
diff --git a/src/gn/command_gen.cc b/src/gn/command_gen.cc
index 7c7fd01..4437236 100644
--- a/src/gn/command_gen.cc
+++ b/src/gn/command_gen.cc
@@ -12,8 +12,10 @@
#include "gn/commands.h"
#include "gn/compile_commands_writer.h"
#include "gn/eclipse_writer.h"
+#include "gn/filesystem_utils.h"
#include "gn/json_project_writer.h"
#include "gn/ninja_target_writer.h"
+#include "gn/ninja_tools.h"
#include "gn/ninja_writer.h"
#include "gn/qt_creator_writer.h"
#include "gn/runtime_deps.h"
@@ -352,6 +354,38 @@
return res;
}
+bool RunNinjaPostProcessTools(const BuildSettings* build_settings,
+ base::FilePath ninja_executable,
+ bool is_regeneration,
+ Err* err) {
+ // If the user did not specify an executable, skip running the post processing
+ // tools. Since these tools can re-write ninja build log and dep logs, it is
+ // really important that ninja executable used for tools matches the
+ // executable that is used for builds.
+ if (ninja_executable.empty()) {
+ return true;
+ }
+
+ base::FilePath build_dir =
+ build_settings->GetFullPath(build_settings->build_dir());
+
+ // If we have a ninja version that supports restat, we should restat the
+ // build.ninja file so the next ninja invocation will use the right mtime. If
+ // gen is being invoked as part of a re-gen (ie, ninja is invoking gn gen),
+ // then we can elide this restat, as ninja will restat build.ninja anyways
+ // after it is complete.
+ if (!is_regeneration &&
+ build_settings->ninja_required_version() >= Version{1, 10, 0}) {
+ std::vector<base::FilePath> files_to_restat{
+ base::FilePath(FILE_PATH_LITERAL("build.ninja"))};
+ if (!InvokeNinjaRestatTool(ninja_executable, build_dir, files_to_restat,
+ err)) {
+ return false;
+ }
+ }
+ return true;
+}
+
} // namespace
const char kGen[] = "gen";
@@ -373,6 +407,14 @@
See "gn help switches" for the common command-line switches.
+General options
+
+ --ninja-executable=<string>
+ Can be used to specify the ninja executable to use. This executable will
+ be used as an IDE option to indicate which ninja to use for building. This
+ executable will also be used as part of the gen process for triggering a
+ restat on generated ninja files.
+
IDE options
GN optionally generates files for IDE. Files won't be overwritten if their
@@ -558,6 +600,14 @@
return 1;
}
+ if (!RunNinjaPostProcessTools(
+ &setup->build_settings(),
+ command_line->GetSwitchValuePath(switches::kNinjaExecutable),
+ command_line->HasSwitch(switches::kRegeneration), &err)) {
+ err.PrintToStdout();
+ return 1;
+ }
+
if (!WriteRuntimeDepsFilesIfNecessary(&setup->build_settings(),
setup->builder(), &err)) {
err.PrintToStdout();
diff --git a/src/gn/ninja_build_writer.cc b/src/gn/ninja_build_writer.cc
index 164c1d5..04e0754 100644
--- a/src/gn/ninja_build_writer.cc
+++ b/src/gn/ninja_build_writer.cc
@@ -113,6 +113,15 @@
}
}
+ // Add the regeneration switch if not already present. This is so that when
+ // the regeneration is invoked by ninja, the gen command is aware that it is a
+ // regeneration invocation and not an user invocation. This allows the gen
+ // command to elide ninja post processing steps that ninja will perform
+ // itself.
+ if (!cmdline.HasSwitch(switches::kRegeneration)) {
+ cmdline.AppendSwitch(switches::kRegeneration);
+ }
+
return cmdline;
}
diff --git a/src/gn/ninja_tools.cc b/src/gn/ninja_tools.cc
new file mode 100644
index 0000000..ed45544
--- /dev/null
+++ b/src/gn/ninja_tools.cc
@@ -0,0 +1,64 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/ninja_tools.h"
+
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/strings/string_number_conversions.h"
+#include "gn/err.h"
+#include "gn/exec_process.h"
+#include "gn/filesystem_utils.h"
+
+namespace {
+
+base::CommandLine CreateNinjaToolCommandLine(const base::FilePath& ninja_executable,
+ const std::string& tool) {
+ base::CommandLine cmdline(ninja_executable);
+ cmdline.SetParseSwitches(false);
+ cmdline.AppendArg("-t");
+ cmdline.AppendArg(tool);
+ return cmdline;
+}
+
+bool RunNinja(const base::CommandLine& cmdline,
+ const base::FilePath& startup_dir,
+ std::string* output,
+ Err* err) {
+ std::string stderr_output;
+
+ int exit_code = 0;
+ if (!internal::ExecProcess(cmdline, startup_dir, output, &stderr_output,
+ &exit_code)) {
+ *err = Err(Location(), "Could not execute Ninja.",
+ "I was trying to execute \"" +
+ FilePathToUTF8(cmdline.GetProgram()) + "\".");
+ return false;
+ }
+
+ if (exit_code != 0) {
+ *err = Err(Location(), "Ninja has quit with exit code " +
+ base::IntToString(exit_code) + ".");
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace
+
+bool InvokeNinjaRestatTool(const base::FilePath& ninja_executable,
+ const base::FilePath& build_dir,
+ const std::vector<base::FilePath>& files_to_restat,
+ Err* err) {
+ base::CommandLine cmdline =
+ CreateNinjaToolCommandLine(ninja_executable, "restat");
+ for (const base::FilePath& file : files_to_restat) {
+ cmdline.AppendArgPath(file);
+ }
+ std::string output;
+ return RunNinja(cmdline, build_dir, &output, err);
+}
diff --git a/src/gn/ninja_tools.h b/src/gn/ninja_tools.h
new file mode 100644
index 0000000..4eda1e1
--- /dev/null
+++ b/src/gn/ninja_tools.h
@@ -0,0 +1,30 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_NINJA_TOOLS_H_
+#define TOOLS_GN_NINJA_TOOLS_H_
+
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "gn/err.h"
+
+// Invokes the ninja restat tool (ie, ninja -C build_dir -t restat). This tool
+// tells ninja that it should check the mtime of the provided files and update
+// the .ninja_log accordingly. This is useful when GN knows that an output file
+// in the ninja graph has been updated without invoking ninja.
+//
+// The best example of this is after gn gen runs, we know that build.ninja has
+// been potentially updated, but ninja will still use the mtime from the
+// .ninja_log and could trigger another re-gen. By telling ninja to restat
+// build.ninja, we can eliminate the extra re-gen.
+//
+// If files_to_restat is empty, ninja will restat all files that have an entry
+// in the .ninja_log.
+bool InvokeNinjaRestatTool(const base::FilePath& ninja_executable,
+ const base::FilePath& build_dir,
+ const std::vector<base::FilePath>& files_to_restat,
+ Err* err);
+
+#endif // TOOLS_GN_NINJA_TOOLS_H_
diff --git a/src/gn/switches.cc b/src/gn/switches.cc
index 0ad3a6b..fb63b30 100644
--- a/src/gn/switches.cc
+++ b/src/gn/switches.cc
@@ -99,6 +99,16 @@
const char kNoColor_HelpShort[] = "--nocolor: Force non-colored output.";
const char kNoColor_Help[] = COLOR_HELP_LONG;
+const char kNinjaExecutable[] = "ninja-executable";
+const char kNinjaExecutable_HelpShort[] =
+ "--ninja-executable: Set the Ninja executable.";
+const char kNinjaExecutable_Help[] =
+ R"(--ninja-executable: Set the Ninja executable.
+
+ When set specifies the Ninja executable that will be used to perform some
+ post-processing on the generated files for more consistent builds.
+)";
+
const char kScriptExecutable[] = "script-executable";
const char kScriptExecutable_HelpShort[] =
"--script-executable: Set the executable used to execute scripts.";
@@ -306,6 +316,7 @@
const char kDefaultToolchain[] = "default-toolchain";
+const char kRegeneration[] = "regeneration";
// -----------------------------------------------------------------------------
SwitchInfo::SwitchInfo() : short_help(""), long_help("") {}
@@ -324,6 +335,7 @@
INSERT_VARIABLE(Dotfile)
INSERT_VARIABLE(FailOnUnusedArgs)
INSERT_VARIABLE(Markdown)
+ INSERT_VARIABLE(NinjaExecutable)
INSERT_VARIABLE(NoColor)
INSERT_VARIABLE(Root)
INSERT_VARIABLE(RootTarget)
diff --git a/src/gn/switches.h b/src/gn/switches.h
index 2f89e50..2382bb7 100644
--- a/src/gn/switches.h
+++ b/src/gn/switches.h
@@ -58,6 +58,10 @@
extern const char kMetaRebaseFiles_HelpShort[];
extern const char kMetaRebaseFiles_Help[];
+extern const char kNinjaExecutable[];
+extern const char kNinjaExecutable_HelpShort[];
+extern const char kNinjaExecutable_Help[];
+
extern const char kNoColor[];
extern const char kNoColor_HelpShort[];
extern const char kNoColor_Help[];
@@ -115,6 +119,13 @@
" Non-wildcard inputs with no explicit toolchain specification will\n" \
" always match only a target in the default toolchain if one exists.\n"
+// This switch is used to signal to the gen command that it is being invoked on
+// a regeneration step. Ie, ninja has realized that build.ninja needs to be
+// generated again and has invoked gn gen. There is no help associated with it
+// because users should not be setting this switch. It is located in this file
+// so it can be shared between command_gen and ninja_build_writer.
+extern const char kRegeneration[];
+
} // namespace switches
#endif // TOOLS_GN_SWITCHES_H_