[Session restore] Rename group name Enabled to Restore.
[chromium-blink-merge.git] / tools / gn / function_exec_script.cc
blob6bc39a6c243cdcbb81e73b3e6076873ccca9a499
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "base/command_line.h"
6 #include "base/files/file_util.h"
7 #include "base/logging.h"
8 #include "base/strings/string_number_conversions.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "base/time/time.h"
11 #include "build/build_config.h"
12 #include "tools/gn/err.h"
13 #include "tools/gn/exec_process.h"
14 #include "tools/gn/filesystem_utils.h"
15 #include "tools/gn/functions.h"
16 #include "tools/gn/input_conversion.h"
17 #include "tools/gn/input_file.h"
18 #include "tools/gn/parse_tree.h"
19 #include "tools/gn/scheduler.h"
20 #include "tools/gn/trace.h"
21 #include "tools/gn/value.h"
23 namespace functions {
25 namespace {
27 bool CheckExecScriptPermissions(const BuildSettings* build_settings,
28 const FunctionCallNode* function,
29 Err* err) {
30 const std::set<SourceFile>* whitelist =
31 build_settings->exec_script_whitelist();
32 if (!whitelist)
33 return true; // No whitelist specified, don't check.
35 LocationRange function_range = function->GetRange();
36 if (!function_range.begin().file())
37 return true; // No file, might be some internal thing, implicitly pass.
39 if (whitelist->find(function_range.begin().file()->name()) !=
40 whitelist->end())
41 return true; // Whitelisted, this is OK.
43 // Disallowed case.
44 *err = Err(function, "Disallowed exec_script call.",
45 "The use of exec_script use is restricted in this build. exec_script\n"
46 "is discouraged because it can slow down the GN run and is easily\n"
47 "abused.\n"
48 "\n"
49 "Generally nontrivial work should be done as build steps rather than\n"
50 "when GN is run. For example, if you need to compute a nontrivial\n"
51 "preprocessor define, it will be better to have an action target\n"
52 "generate a header containing the define rather than blocking the GN\n"
53 "run to compute the value.\n"
54 "\n"
55 "The allowed callers of exec_script is maintained in the \"//.gn\" file\n"
56 "if you need to modify the whitelist.");
57 return false;
60 } // namespace
62 const char kExecScript[] = "exec_script";
63 const char kExecScript_HelpShort[] =
64 "exec_script: Synchronously run a script and return the output.";
65 const char kExecScript_Help[] =
66 "exec_script: Synchronously run a script and return the output.\n"
67 "\n"
68 " exec_script(filename,\n"
69 " arguments = [],\n"
70 " input_conversion = \"\",\n"
71 " file_dependencies = [])\n"
72 "\n"
73 " Runs the given script, returning the stdout of the script. The build\n"
74 " generation will fail if the script does not exist or returns a nonzero\n"
75 " exit code.\n"
76 "\n"
77 " The current directory when executing the script will be the root\n"
78 " build directory. If you are passing file names, you will want to use\n"
79 " the rebase_path() function to make file names relative to this\n"
80 " path (see \"gn help rebase_path\").\n"
81 "\n"
82 "Arguments:\n"
83 "\n"
84 " filename:\n"
85 " File name of python script to execute. Non-absolute names will\n"
86 " be treated as relative to the current build file.\n"
87 "\n"
88 " arguments:\n"
89 " A list of strings to be passed to the script as arguments.\n"
90 " May be unspecified or the empty list which means no arguments.\n"
91 "\n"
92 " input_conversion:\n"
93 " Controls how the file is read and parsed.\n"
94 " See \"gn help input_conversion\".\n"
95 "\n"
96 " If unspecified, defaults to the empty string which causes the\n"
97 " script result to be discarded. exec script will return None.\n"
98 "\n"
99 " dependencies:\n"
100 " (Optional) A list of files that this script reads or otherwise\n"
101 " depends on. These dependencies will be added to the build result\n"
102 " such that if any of them change, the build will be regenerated and\n"
103 " the script will be re-run.\n"
104 "\n"
105 " The script itself will be an implicit dependency so you do not\n"
106 " need to list it.\n"
107 "\n"
108 "Example:\n"
109 "\n"
110 " all_lines = exec_script(\n"
111 " \"myscript.py\", [some_input], \"list lines\",\n"
112 " [ rebase_path(\"data_file.txt\", root_build_dir) ])\n"
113 "\n"
114 " # This example just calls the script with no arguments and discards\n"
115 " # the result.\n"
116 " exec_script(\"//foo/bar/myscript.py\")\n";
118 Value RunExecScript(Scope* scope,
119 const FunctionCallNode* function,
120 const std::vector<Value>& args,
121 Err* err) {
122 if (args.size() < 1 || args.size() > 4) {
123 *err = Err(function->function(), "Wrong number of arguments to exec_script",
124 "I expected between one and four arguments.");
125 return Value();
128 const Settings* settings = scope->settings();
129 const BuildSettings* build_settings = settings->build_settings();
130 const SourceDir& cur_dir = scope->GetSourceDir();
132 if (!CheckExecScriptPermissions(build_settings, function, err))
133 return Value();
135 // Find the python script to run.
136 if (!args[0].VerifyTypeIs(Value::STRING, err))
137 return Value();
138 SourceFile script_source =
139 cur_dir.ResolveRelativeFile(args[0].string_value(),
140 scope->settings()->build_settings()->root_path_utf8());
141 base::FilePath script_path = build_settings->GetFullPath(script_source);
142 if (!build_settings->secondary_source_path().empty() &&
143 !base::PathExists(script_path)) {
144 // Fall back to secondary source root when the file doesn't exist.
145 script_path = build_settings->GetFullPathSecondary(script_source);
148 ScopedTrace trace(TraceItem::TRACE_SCRIPT_EXECUTE, script_source.value());
149 trace.SetToolchain(settings->toolchain_label());
151 // Add all dependencies of this script, including the script itself, to the
152 // build deps.
153 g_scheduler->AddGenDependency(script_path);
154 if (args.size() == 4) {
155 const Value& deps_value = args[3];
156 if (!deps_value.VerifyTypeIs(Value::LIST, err))
157 return Value();
159 for (const auto& dep : deps_value.list_value()) {
160 if (!dep.VerifyTypeIs(Value::STRING, err))
161 return Value();
162 g_scheduler->AddGenDependency(
163 build_settings->GetFullPath(cur_dir.ResolveRelativeFile(
164 dep.string_value(),
165 scope->settings()->build_settings()->root_path_utf8())));
169 // Make the command line.
170 const base::FilePath& python_path = build_settings->python_path();
171 base::CommandLine cmdline(python_path);
172 cmdline.AppendArgPath(script_path);
174 if (args.size() >= 2) {
175 // Optional command-line arguments to the script.
176 const Value& script_args = args[1];
177 if (!script_args.VerifyTypeIs(Value::LIST, err))
178 return Value();
179 for (const auto& arg : script_args.list_value()) {
180 if (!arg.VerifyTypeIs(Value::STRING, err))
181 return Value();
182 cmdline.AppendArg(arg.string_value());
186 // Log command line for debugging help.
187 trace.SetCommandLine(cmdline);
188 base::TimeTicks begin_exec;
189 if (g_scheduler->verbose_logging()) {
190 #if defined(OS_WIN)
191 g_scheduler->Log("Pythoning",
192 base::UTF16ToUTF8(cmdline.GetCommandLineString()));
193 #else
194 g_scheduler->Log("Pythoning", cmdline.GetCommandLineString());
195 #endif
196 begin_exec = base::TimeTicks::Now();
199 base::FilePath startup_dir =
200 build_settings->GetFullPath(build_settings->build_dir());
201 // The first time a build is run, no targets will have been written so the
202 // build output directory won't exist. We need to make sure it does before
203 // running any scripts with this as its startup directory, although it will
204 // be relatively rare that the directory won't exist by the time we get here.
206 // If this shows up on benchmarks, we can cache whether we've done this
207 // or not and skip creating the directory.
208 base::CreateDirectory(startup_dir);
210 // Execute the process.
211 // TODO(brettw) set the environment block.
212 std::string output;
213 std::string stderr_output;
214 int exit_code = 0;
215 if (!internal::ExecProcess(
216 cmdline, startup_dir, &output, &stderr_output, &exit_code)) {
217 *err = Err(function->function(), "Could not execute python.",
218 "I was trying to execute \"" + FilePathToUTF8(python_path) + "\".");
219 return Value();
221 if (g_scheduler->verbose_logging()) {
222 g_scheduler->Log("Pythoning", script_source.value() + " took " +
223 base::Int64ToString(
224 (base::TimeTicks::Now() - begin_exec).InMilliseconds()) +
225 "ms");
228 if (exit_code != 0) {
229 std::string msg = "Current dir: " + FilePathToUTF8(startup_dir) +
230 "\nCommand: " + FilePathToUTF8(cmdline.GetCommandLineString()) +
231 "\nReturned " + base::IntToString(exit_code);
232 if (!output.empty())
233 msg += " and printed out:\n\n" + output;
234 else
235 msg += ".";
236 if (!stderr_output.empty())
237 msg += "\nstderr:\n\n" + stderr_output;
239 *err = Err(function->function(), "Script returned non-zero exit code.",
240 msg);
241 return Value();
244 // Default to None value for the input conversion if unspecified.
245 return ConvertInputToValue(scope->settings(), output, function,
246 args.size() >= 3 ? args[2] : Value(), err);
249 } // namespace functions