| OLD | NEW |
| (Empty) |
| 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. | |
| 4 | |
| 5 #include "tools/gn/file_template.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 #include <iostream> | |
| 9 | |
| 10 #include "tools/gn/escape.h" | |
| 11 #include "tools/gn/filesystem_utils.h" | |
| 12 #include "tools/gn/string_utils.h" | |
| 13 #include "tools/gn/target.h" | |
| 14 | |
| 15 const char FileTemplate::kSource[] = "{{source}}"; | |
| 16 const char FileTemplate::kSourceNamePart[] = "{{source_name_part}}"; | |
| 17 const char FileTemplate::kSourceFilePart[] = "{{source_file_part}}"; | |
| 18 const char FileTemplate::kSourceDir[] = "{{source_dir}}"; | |
| 19 const char FileTemplate::kRootRelDir[] = "{{source_root_relative_dir}}"; | |
| 20 const char FileTemplate::kSourceGenDir[] = "{{source_gen_dir}}"; | |
| 21 const char FileTemplate::kSourceOutDir[] = "{{source_out_dir}}"; | |
| 22 | |
| 23 const char kSourceExpansion_Help[] = | |
| 24 "How Source Expansion Works\n" | |
| 25 "\n" | |
| 26 " Source expansion is used for the action_foreach and copy target types\n" | |
| 27 " to map source file names to output file names or arguments.\n" | |
| 28 "\n" | |
| 29 " To perform source expansion in the outputs, GN maps every entry in the\n" | |
| 30 " sources to every entry in the outputs list, producing the cross\n" | |
| 31 " product of all combinations, expanding placeholders (see below).\n" | |
| 32 "\n" | |
| 33 " Source expansion in the args works similarly, but performing the\n" | |
| 34 " placeholder substitution produces a different set of arguments for\n" | |
| 35 " each invocation of the script.\n" | |
| 36 "\n" | |
| 37 " If no placeholders are found, the outputs or args list will be treated\n" | |
| 38 " as a static list of literal file names that do not depend on the\n" | |
| 39 " sources.\n" | |
| 40 "\n" | |
| 41 " See \"gn help copy\" and \"gn help action_foreach\" for more on how\n" | |
| 42 " this is applied.\n" | |
| 43 "\n" | |
| 44 "Placeholders\n" | |
| 45 "\n" | |
| 46 " {{source}}\n" | |
| 47 " The name of the source file including directory (*). This will\n" | |
| 48 " generally be used for specifying inputs to a script in the\n" | |
| 49 " \"args\" variable.\n" | |
| 50 " \"//foo/bar/baz.txt\" => \"../../foo/bar/baz.txt\"\n" | |
| 51 "\n" | |
| 52 " {{source_file_part}}\n" | |
| 53 " The file part of the source including the extension.\n" | |
| 54 " \"//foo/bar/baz.txt\" => \"baz.txt\"\n" | |
| 55 "\n" | |
| 56 " {{source_name_part}}\n" | |
| 57 " The filename part of the source file with no directory or\n" | |
| 58 " extension. This will generally be used for specifying a\n" | |
| 59 " transformation from a soruce file to a destination file with the\n" | |
| 60 " same name but different extension.\n" | |
| 61 " \"//foo/bar/baz.txt\" => \"baz\"\n" | |
| 62 "\n" | |
| 63 " {{source_dir}}\n" | |
| 64 " The directory (*) containing the source file with no\n" | |
| 65 " trailing slash.\n" | |
| 66 " \"//foo/bar/baz.txt\" => \"../../foo/bar\"\n" | |
| 67 "\n" | |
| 68 " {{source_root_relative_dir}}\n" | |
| 69 " The path to the source file's directory relative to the source\n" | |
| 70 " root, with no leading \"//\" or trailing slashes. If the path is\n" | |
| 71 " system-absolute, (beginning in a single slash) this will just\n" | |
| 72 " return the path with no trailing slash. This value will always\n" | |
| 73 " be the same, regardless of whether it appears in the \"outputs\"\n" | |
| 74 " or \"args\" section.\n" | |
| 75 " \"//foo/bar/baz.txt\" => \"foo/bar\"\n" | |
| 76 "\n" | |
| 77 " {{source_gen_dir}}\n" | |
| 78 " The generated file directory (*) corresponding to the source\n" | |
| 79 " file's path. This will be different than the target's generated\n" | |
| 80 " file directory if the source file is in a different directory\n" | |
| 81 " than the BUILD.gn file.\n" | |
| 82 " \"//foo/bar/baz.txt\" => \"gen/foo/bar\"\n" | |
| 83 "\n" | |
| 84 " {{source_out_dir}}\n" | |
| 85 " The object file directory (*) corresponding to the source file's\n" | |
| 86 " path, relative to the build directory. this us be different than\n" | |
| 87 " the target's out directory if the source file is in a different\n" | |
| 88 " directory than the build.gn file.\n" | |
| 89 " \"//foo/bar/baz.txt\" => \"obj/foo/bar\"\n" | |
| 90 "\n" | |
| 91 "(*) Note on directories\n" | |
| 92 "\n" | |
| 93 " Paths containing directories (except the source_root_relative_dir)\n" | |
| 94 " will be different depending on what context the expansion is evaluated\n" | |
| 95 " in. Generally it should \"just work\" but it means you can't\n" | |
| 96 " concatenate strings containing these values with reasonable results.\n" | |
| 97 "\n" | |
| 98 " Details: source expansions can be used in the \"outputs\" variable,\n" | |
| 99 " the \"args\" variable, and in calls to \"process_file_template\". The\n" | |
| 100 " \"args\" are passed to a script which is run from the build directory,\n" | |
| 101 " so these directories will relative to the build directory for the\n" | |
| 102 " script to find. In the other cases, the directories will be source-\n" | |
| 103 " absolute (begin with a \"//\") because the results of those expansions\n" | |
| 104 " will be handled by GN internally.\n" | |
| 105 "\n" | |
| 106 "Examples\n" | |
| 107 "\n" | |
| 108 " Non-varying outputs:\n" | |
| 109 " action(\"hardcoded_outputs\") {\n" | |
| 110 " sources = [ \"input1.idl\", \"input2.idl\" ]\n" | |
| 111 " outputs = [ \"$target_out_dir/output1.dat\",\n" | |
| 112 " \"$target_out_dir/output2.dat\" ]\n" | |
| 113 " }\n" | |
| 114 " The outputs in this case will be the two literal files given.\n" | |
| 115 "\n" | |
| 116 " Varying outputs:\n" | |
| 117 " action_foreach(\"varying_outputs\") {\n" | |
| 118 " sources = [ \"input1.idl\", \"input2.idl\" ]\n" | |
| 119 " outputs = [ \"{{source_gen_dir}}/{{source_name_part}}.h\",\n" | |
| 120 " \"{{source_gen_dir}}/{{source_name_part}}.cc\" ]\n" | |
| 121 " }\n" | |
| 122 " Performing source expansion will result in the following output names:\n" | |
| 123 " //out/Debug/obj/mydirectory/input1.h\n" | |
| 124 " //out/Debug/obj/mydirectory/input1.cc\n" | |
| 125 " //out/Debug/obj/mydirectory/input2.h\n" | |
| 126 " //out/Debug/obj/mydirectory/input2.cc\n"; | |
| 127 | |
| 128 FileTemplate::FileTemplate(const Settings* settings, | |
| 129 const Value& t, | |
| 130 OutputStyle output_style, | |
| 131 const SourceDir& relative_to, | |
| 132 Err* err) | |
| 133 : settings_(settings), | |
| 134 output_style_(output_style), | |
| 135 relative_to_(relative_to), | |
| 136 has_substitutions_(false) { | |
| 137 std::fill(types_required_, &types_required_[Subrange::NUM_TYPES], false); | |
| 138 ParseInput(t, err); | |
| 139 } | |
| 140 | |
| 141 FileTemplate::FileTemplate(const Settings* settings, | |
| 142 const std::vector<std::string>& t, | |
| 143 OutputStyle output_style, | |
| 144 const SourceDir& relative_to) | |
| 145 : settings_(settings), | |
| 146 output_style_(output_style), | |
| 147 relative_to_(relative_to), | |
| 148 has_substitutions_(false) { | |
| 149 std::fill(types_required_, &types_required_[Subrange::NUM_TYPES], false); | |
| 150 for (size_t i = 0; i < t.size(); i++) | |
| 151 ParseOneTemplateString(t[i]); | |
| 152 } | |
| 153 | |
| 154 FileTemplate::FileTemplate(const Settings* settings, | |
| 155 const std::vector<SourceFile>& t, | |
| 156 OutputStyle output_style, | |
| 157 const SourceDir& relative_to) | |
| 158 : settings_(settings), | |
| 159 output_style_(output_style), | |
| 160 relative_to_(relative_to), | |
| 161 has_substitutions_(false) { | |
| 162 std::fill(types_required_, &types_required_[Subrange::NUM_TYPES], false); | |
| 163 for (size_t i = 0; i < t.size(); i++) | |
| 164 ParseOneTemplateString(t[i].value()); | |
| 165 } | |
| 166 | |
| 167 FileTemplate::~FileTemplate() { | |
| 168 } | |
| 169 | |
| 170 // static | |
| 171 FileTemplate FileTemplate::GetForTargetOutputs(const Target* target) { | |
| 172 const std::vector<std::string>& outputs = target->action_values().outputs(); | |
| 173 std::vector<std::string> output_template_args; | |
| 174 for (size_t i = 0; i < outputs.size(); i++) | |
| 175 output_template_args.push_back(outputs[i]); | |
| 176 return FileTemplate(target->settings(), output_template_args, | |
| 177 OUTPUT_ABSOLUTE, SourceDir()); | |
| 178 } | |
| 179 | |
| 180 bool FileTemplate::IsTypeUsed(Subrange::Type type) const { | |
| 181 DCHECK(type > Subrange::LITERAL && type < Subrange::NUM_TYPES); | |
| 182 return types_required_[type]; | |
| 183 } | |
| 184 | |
| 185 void FileTemplate::Apply(const SourceFile& source, | |
| 186 std::vector<std::string>* output) const { | |
| 187 // Compute all substitutions needed so we can just do substitutions below. | |
| 188 // We skip the LITERAL one since that varies each time. | |
| 189 std::string subst[Subrange::NUM_TYPES]; | |
| 190 for (int i = 1; i < Subrange::NUM_TYPES; i++) { | |
| 191 if (types_required_[i]) { | |
| 192 subst[i] = GetSubstitution(settings_, source, | |
| 193 static_cast<Subrange::Type>(i), | |
| 194 output_style_, relative_to_); | |
| 195 } | |
| 196 } | |
| 197 | |
| 198 size_t first_output_index = output->size(); | |
| 199 output->resize(output->size() + templates_.container().size()); | |
| 200 for (size_t template_i = 0; | |
| 201 template_i < templates_.container().size(); template_i++) { | |
| 202 const Template& t = templates_[template_i]; | |
| 203 std::string& cur_output = (*output)[first_output_index + template_i]; | |
| 204 for (size_t subrange_i = 0; subrange_i < t.container().size(); | |
| 205 subrange_i++) { | |
| 206 if (t[subrange_i].type == Subrange::LITERAL) | |
| 207 cur_output.append(t[subrange_i].literal); | |
| 208 else | |
| 209 cur_output.append(subst[t[subrange_i].type]); | |
| 210 } | |
| 211 } | |
| 212 } | |
| 213 | |
| 214 void FileTemplate::WriteWithNinjaExpansions(std::ostream& out) const { | |
| 215 EscapeOptions escape_options; | |
| 216 escape_options.mode = ESCAPE_NINJA_COMMAND; | |
| 217 escape_options.inhibit_quoting = true; | |
| 218 | |
| 219 for (size_t template_i = 0; | |
| 220 template_i < templates_.container().size(); template_i++) { | |
| 221 out << " "; // Separate args with spaces. | |
| 222 | |
| 223 const Template& t = templates_[template_i]; | |
| 224 | |
| 225 // Escape each subrange into a string. Since we're writing out Ninja | |
| 226 // variables, we can't quote the whole thing, so we write in pieces, only | |
| 227 // escaping the literals, and then quoting the whole thing at the end if | |
| 228 // necessary. | |
| 229 bool needs_quoting = false; | |
| 230 std::string item_str; | |
| 231 for (size_t subrange_i = 0; subrange_i < t.container().size(); | |
| 232 subrange_i++) { | |
| 233 if (t[subrange_i].type == Subrange::LITERAL) { | |
| 234 bool cur_needs_quoting = false; | |
| 235 item_str.append(EscapeString(t[subrange_i].literal, escape_options, | |
| 236 &cur_needs_quoting)); | |
| 237 needs_quoting |= cur_needs_quoting; | |
| 238 } else { | |
| 239 // Don't escape this since we need to preserve the $. | |
| 240 item_str.append("${"); | |
| 241 item_str.append(GetNinjaVariableNameForType(t[subrange_i].type)); | |
| 242 item_str.append("}"); | |
| 243 } | |
| 244 } | |
| 245 | |
| 246 if (needs_quoting || item_str.empty()) { | |
| 247 // Need to shell quote the whole string. We also need to quote empty | |
| 248 // strings or it would be impossible to pass "" as a command-line | |
| 249 // argument. | |
| 250 out << '"' << item_str << '"'; | |
| 251 } else { | |
| 252 out << item_str; | |
| 253 } | |
| 254 } | |
| 255 } | |
| 256 | |
| 257 void FileTemplate::WriteNinjaVariablesForSubstitution( | |
| 258 std::ostream& out, | |
| 259 const SourceFile& source, | |
| 260 const EscapeOptions& escape_options) const { | |
| 261 for (int i = 1; i < Subrange::NUM_TYPES; i++) { | |
| 262 if (types_required_[i]) { | |
| 263 Subrange::Type type = static_cast<Subrange::Type>(i); | |
| 264 out << " " << GetNinjaVariableNameForType(type) << " = "; | |
| 265 EscapeStringToStream( | |
| 266 out, | |
| 267 GetSubstitution(settings_, source, type, output_style_, relative_to_), | |
| 268 escape_options); | |
| 269 out << std::endl; | |
| 270 } | |
| 271 } | |
| 272 } | |
| 273 | |
| 274 // static | |
| 275 const char* FileTemplate::GetNinjaVariableNameForType(Subrange::Type type) { | |
| 276 switch (type) { | |
| 277 case Subrange::SOURCE: | |
| 278 return "source"; | |
| 279 case Subrange::NAME_PART: | |
| 280 return "source_name_part"; | |
| 281 case Subrange::FILE_PART: | |
| 282 return "source_file_part"; | |
| 283 case Subrange::SOURCE_DIR: | |
| 284 return "source_dir"; | |
| 285 case Subrange::ROOT_RELATIVE_DIR: | |
| 286 return "source_root_rel_dir"; | |
| 287 case Subrange::SOURCE_GEN_DIR: | |
| 288 return "source_gen_dir"; | |
| 289 case Subrange::SOURCE_OUT_DIR: | |
| 290 return "source_out_dir"; | |
| 291 | |
| 292 default: | |
| 293 NOTREACHED(); | |
| 294 } | |
| 295 return ""; | |
| 296 } | |
| 297 | |
| 298 // static | |
| 299 std::string FileTemplate::GetSubstitution(const Settings* settings, | |
| 300 const SourceFile& source, | |
| 301 Subrange::Type type, | |
| 302 OutputStyle output_style, | |
| 303 const SourceDir& relative_to) { | |
| 304 std::string to_rebase; | |
| 305 switch (type) { | |
| 306 case Subrange::SOURCE: | |
| 307 if (source.is_system_absolute()) | |
| 308 return source.value(); | |
| 309 to_rebase = source.value(); | |
| 310 break; | |
| 311 | |
| 312 case Subrange::NAME_PART: | |
| 313 return FindFilenameNoExtension(&source.value()).as_string(); | |
| 314 | |
| 315 case Subrange::FILE_PART: | |
| 316 return source.GetName(); | |
| 317 | |
| 318 case Subrange::SOURCE_DIR: | |
| 319 if (source.is_system_absolute()) | |
| 320 return DirectoryWithNoLastSlash(source.GetDir()); | |
| 321 to_rebase = DirectoryWithNoLastSlash(source.GetDir()); | |
| 322 break; | |
| 323 | |
| 324 case Subrange::ROOT_RELATIVE_DIR: | |
| 325 if (source.is_system_absolute()) | |
| 326 return DirectoryWithNoLastSlash(source.GetDir()); | |
| 327 return RebaseSourceAbsolutePath( | |
| 328 DirectoryWithNoLastSlash(source.GetDir()), SourceDir("//")); | |
| 329 | |
| 330 case Subrange::SOURCE_GEN_DIR: | |
| 331 to_rebase = DirectoryWithNoLastSlash( | |
| 332 GetGenDirForSourceDir(settings, source.GetDir())); | |
| 333 break; | |
| 334 | |
| 335 case Subrange::SOURCE_OUT_DIR: | |
| 336 to_rebase = DirectoryWithNoLastSlash( | |
| 337 GetOutputDirForSourceDir(settings, source.GetDir())); | |
| 338 break; | |
| 339 | |
| 340 default: | |
| 341 NOTREACHED(); | |
| 342 return std::string(); | |
| 343 } | |
| 344 | |
| 345 // If we get here, the result is a path that should be made relative or | |
| 346 // absolute according to the output_style. Other cases (just file name or | |
| 347 // extension extraction) will have been handled via early return above. | |
| 348 if (output_style == OUTPUT_ABSOLUTE) | |
| 349 return to_rebase; | |
| 350 return RebaseSourceAbsolutePath(to_rebase, relative_to); | |
| 351 } | |
| 352 | |
| 353 void FileTemplate::ParseInput(const Value& value, Err* err) { | |
| 354 switch (value.type()) { | |
| 355 case Value::STRING: | |
| 356 ParseOneTemplateString(value.string_value()); | |
| 357 break; | |
| 358 case Value::LIST: | |
| 359 for (size_t i = 0; i < value.list_value().size(); i++) { | |
| 360 if (!value.list_value()[i].VerifyTypeIs(Value::STRING, err)) | |
| 361 return; | |
| 362 ParseOneTemplateString(value.list_value()[i].string_value()); | |
| 363 } | |
| 364 break; | |
| 365 default: | |
| 366 *err = Err(value, "File template must be a string or list.", | |
| 367 "A sarcastic comment about your skills goes here."); | |
| 368 } | |
| 369 } | |
| 370 | |
| 371 void FileTemplate::ParseOneTemplateString(const std::string& str) { | |
| 372 templates_.container().resize(templates_.container().size() + 1); | |
| 373 Template& t = templates_[templates_.container().size() - 1]; | |
| 374 | |
| 375 size_t cur = 0; | |
| 376 while (true) { | |
| 377 size_t next = str.find("{{", cur); | |
| 378 | |
| 379 // Pick up everything from the previous spot to here as a literal. | |
| 380 if (next == std::string::npos) { | |
| 381 if (cur != str.size()) | |
| 382 t.container().push_back(Subrange(Subrange::LITERAL, str.substr(cur))); | |
| 383 break; | |
| 384 } else if (next > cur) { | |
| 385 t.container().push_back( | |
| 386 Subrange(Subrange::LITERAL, str.substr(cur, next - cur))); | |
| 387 } | |
| 388 | |
| 389 // Given the name of the string constant and enum for a template parameter, | |
| 390 // checks for it and stores it. Writing this as a function requires passing | |
| 391 // the entire state of this function as arguments, so this actually ends | |
| 392 // up being more clear. | |
| 393 #define IF_MATCH_THEN_STORE(const_name, enum_name) \ | |
| 394 if (str.compare(next, arraysize(const_name) - 1, const_name) == 0) { \ | |
| 395 t.container().push_back(Subrange(Subrange::enum_name)); \ | |
| 396 types_required_[Subrange::enum_name] = true; \ | |
| 397 has_substitutions_ = true; \ | |
| 398 cur = next + arraysize(const_name) - 1; \ | |
| 399 } | |
| 400 | |
| 401 // Decode the template param. | |
| 402 IF_MATCH_THEN_STORE(kSource, SOURCE) | |
| 403 else IF_MATCH_THEN_STORE(kSourceNamePart, NAME_PART) | |
| 404 else IF_MATCH_THEN_STORE(kSourceFilePart, FILE_PART) | |
| 405 else IF_MATCH_THEN_STORE(kSourceDir, SOURCE_DIR) | |
| 406 else IF_MATCH_THEN_STORE(kRootRelDir, ROOT_RELATIVE_DIR) | |
| 407 else IF_MATCH_THEN_STORE(kSourceGenDir, SOURCE_GEN_DIR) | |
| 408 else IF_MATCH_THEN_STORE(kSourceOutDir, SOURCE_OUT_DIR) | |
| 409 else { | |
| 410 // If it's not a match, treat it like a one-char literal (this will be | |
| 411 // rare, so it's not worth the bother to add to the previous literal) so | |
| 412 // we can keep going. | |
| 413 t.container().push_back(Subrange(Subrange::LITERAL, "{")); | |
| 414 cur = next + 1; | |
| 415 } | |
| 416 | |
| 417 #undef IF_MATCH_THEN_STORE | |
| 418 } | |
| 419 } | |
| OLD | NEW |