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 relative to the root build output\n" 48 " directory (which is the current directory when running compilers\n" 49 " and scripts). This will generally be used for specifying inputs\n" 50 " to a script in the \"args\" variable.\n" 51 " \"//foo/bar/baz.txt\" => \"../../foo/bar/baz.txt\"\n" 52 "\n" 53 " {{source_file_part}}\n" 54 " The file part of the source including the extension.\n" 55 " \"//foo/bar/baz.txt\" => \"baz.txt\"\n" 56 "\n" 57 " {{source_name_part}}\n" 58 " The filename part of the source file with no directory or\n" 59 " extension. This will generally be used for specifying a\n" 60 " transformation from a soruce file to a destination file with the\n" 61 " same name but different extension.\n" 62 " \"//foo/bar/baz.txt\" => \"baz\"\n" 63 "\n" 64 " {{source_dir}}\n" 65 " The directory containing the source file, relative to the build\n" 66 " directory, with no trailing slash.\n" 67 " \"//foo/bar/baz.txt\" => \"../../foo/bar\"\n" 68 "\n" 69 " {{source_root_relative_dir}}\n" 70 " The path to the source file's directory relative to the source\n" 71 " root, with no leading \"//\" or trailing slashes. If the path is\n" 72 " system-absolute, (beginning in a single slash) this will just\n" 73 " return the path with no trailing slash.\n" 74 " \"//foo/bar/baz.txt\" => \"foo/bar\"\n" 75 "\n" 76 " {{source_gen_dir}}\n" 77 " The generated file directory corresponding to the source file's\n" 78 " path, relative to the build directory. This will be different than\n" 79 " the target's generated file directory if the source file is in a\n" 80 " different directory than the build.gn file. If the input path is\n" 81 " system absolute, this will return the root generated file\n" 82 " directory." 83 " \"//foo/bar/baz.txt\" => \"gen/foo/bar\"\n" 84 "\n" 85 " {{source_out_dir}}\n" 86 " The object file directory corresponding to the source file's\n" 87 " path, relative to the build directory. this us be different than\n" 88 " the target's out directory if the source file is in a different\n" 89 " directory than the build.gn file. if the input path is system\n" 90 " absolute, this will return the root generated file directory.\n" 91 " \"//foo/bar/baz.txt\" => \"obj/foo/bar\"\n" 92 "\n" 93 "Examples\n" 94 "\n" 95 " Non-varying outputs:\n" 96 " action(\"hardcoded_outputs\") {\n" 97 " sources = [ \"input1.idl\", \"input2.idl\" ]\n" 98 " outputs = [ \"$target_out_dir/output1.dat\",\n" 99 " \"$target_out_dir/output2.dat\" ]\n" 100 " }\n" 101 " The outputs in this case will be the two literal files given.\n" 102 "\n" 103 " Varying outputs:\n" 104 " action_foreach(\"varying_outputs\") {\n" 105 " sources = [ \"input1.idl\", \"input2.idl\" ]\n" 106 " outputs = [ \"$target_out_dir/{{source_name_part}}.h\",\n" 107 " \"$target_out_dir/{{source_name_part}}.cc\" ]\n" 108 " }\n" 109 " Performing source expansion will result in the following output names:\n" 110 " //out/Debug/obj/mydirectory/input1.h\n" 111 " //out/Debug/obj/mydirectory/input1.cc\n" 112 " //out/Debug/obj/mydirectory/input2.h\n" 113 " //out/Debug/obj/mydirectory/input2.cc\n"; 114 115 FileTemplate::FileTemplate(const Settings* settings, const Value& t, Err* err) 116 : settings_(settings), 117 has_substitutions_(false) { 118 std::fill(types_required_, &types_required_[Subrange::NUM_TYPES], false); 119 ParseInput(t, err); 120 } 121 122 FileTemplate::FileTemplate(const Settings* settings, 123 const std::vector<std::string>& t) 124 : settings_(settings), 125 has_substitutions_(false) { 126 std::fill(types_required_, &types_required_[Subrange::NUM_TYPES], false); 127 for (size_t i = 0; i < t.size(); i++) 128 ParseOneTemplateString(t[i]); 129 } 130 131 FileTemplate::FileTemplate(const Settings* settings, 132 const std::vector<SourceFile>& t) 133 : settings_(settings), 134 has_substitutions_(false) { 135 std::fill(types_required_, &types_required_[Subrange::NUM_TYPES], false); 136 for (size_t i = 0; i < t.size(); i++) 137 ParseOneTemplateString(t[i].value()); 138 } 139 140 FileTemplate::~FileTemplate() { 141 } 142 143 // static 144 FileTemplate FileTemplate::GetForTargetOutputs(const Target* target) { 145 const Target::FileList& outputs = target->action_values().outputs(); 146 std::vector<std::string> output_template_args; 147 for (size_t i = 0; i < outputs.size(); i++) 148 output_template_args.push_back(outputs[i].value()); 149 return FileTemplate(target->settings(), output_template_args); 150 } 151 152 bool FileTemplate::IsTypeUsed(Subrange::Type type) const { 153 DCHECK(type > Subrange::LITERAL && type < Subrange::NUM_TYPES); 154 return types_required_[type]; 155 } 156 157 void FileTemplate::Apply(const SourceFile& source, 158 std::vector<std::string>* output) const { 159 // Compute all substitutions needed so we can just do substitutions below. 160 // We skip the LITERAL one since that varies each time. 161 std::string subst[Subrange::NUM_TYPES]; 162 for (int i = 1; i < Subrange::NUM_TYPES; i++) { 163 if (types_required_[i]) { 164 subst[i] = 165 GetSubstitution(settings_, source, static_cast<Subrange::Type>(i)); 166 } 167 } 168 169 size_t first_output_index = output->size(); 170 output->resize(output->size() + templates_.container().size()); 171 for (size_t template_i = 0; 172 template_i < templates_.container().size(); template_i++) { 173 const Template& t = templates_[template_i]; 174 std::string& cur_output = (*output)[first_output_index + template_i]; 175 for (size_t subrange_i = 0; subrange_i < t.container().size(); 176 subrange_i++) { 177 if (t[subrange_i].type == Subrange::LITERAL) 178 cur_output.append(t[subrange_i].literal); 179 else 180 cur_output.append(subst[t[subrange_i].type]); 181 } 182 } 183 } 184 185 void FileTemplate::WriteWithNinjaExpansions(std::ostream& out) const { 186 EscapeOptions escape_options; 187 escape_options.mode = ESCAPE_NINJA_COMMAND; 188 escape_options.inhibit_quoting = true; 189 190 for (size_t template_i = 0; 191 template_i < templates_.container().size(); template_i++) { 192 out << " "; // Separate args with spaces. 193 194 const Template& t = templates_[template_i]; 195 196 // Escape each subrange into a string. Since we're writing out Ninja 197 // variables, we can't quote the whole thing, so we write in pieces, only 198 // escaping the literals, and then quoting the whole thing at the end if 199 // necessary. 200 bool needs_quoting = false; 201 std::string item_str; 202 for (size_t subrange_i = 0; subrange_i < t.container().size(); 203 subrange_i++) { 204 if (t[subrange_i].type == Subrange::LITERAL) { 205 bool cur_needs_quoting = false; 206 item_str.append(EscapeString(t[subrange_i].literal, escape_options, 207 &cur_needs_quoting)); 208 needs_quoting |= cur_needs_quoting; 209 } else { 210 // Don't escape this since we need to preserve the $. 211 item_str.append("${"); 212 item_str.append(GetNinjaVariableNameForType(t[subrange_i].type)); 213 item_str.append("}"); 214 } 215 } 216 217 if (needs_quoting || item_str.empty()) { 218 // Need to shell quote the whole string. We also need to quote empty 219 // strings or it would be impossible to pass "" as a command-line 220 // argument. 221 out << '"' << item_str << '"'; 222 } else { 223 out << item_str; 224 } 225 } 226 } 227 228 void FileTemplate::WriteNinjaVariablesForSubstitution( 229 std::ostream& out, 230 const Settings* settings, 231 const SourceFile& source, 232 const EscapeOptions& escape_options) const { 233 for (int i = 1; i < Subrange::NUM_TYPES; i++) { 234 if (types_required_[i]) { 235 Subrange::Type type = static_cast<Subrange::Type>(i); 236 out << " " << GetNinjaVariableNameForType(type) << " = "; 237 EscapeStringToStream(out, GetSubstitution(settings, source, type), 238 escape_options); 239 out << std::endl; 240 } 241 } 242 } 243 244 // static 245 const char* FileTemplate::GetNinjaVariableNameForType(Subrange::Type type) { 246 switch (type) { 247 case Subrange::SOURCE: 248 return "source"; 249 case Subrange::NAME_PART: 250 return "source_name_part"; 251 case Subrange::FILE_PART: 252 return "source_file_part"; 253 case Subrange::SOURCE_DIR: 254 return "source_dir"; 255 case Subrange::ROOT_RELATIVE_DIR: 256 return "source_root_rel_dir"; 257 case Subrange::SOURCE_GEN_DIR: 258 return "source_gen_dir"; 259 case Subrange::SOURCE_OUT_DIR: 260 return "source_out_dir"; 261 262 default: 263 NOTREACHED(); 264 } 265 return ""; 266 } 267 268 // static 269 std::string FileTemplate::GetSubstitution(const Settings* settings, 270 const SourceFile& source, 271 Subrange::Type type) { 272 switch (type) { 273 case Subrange::SOURCE: 274 if (source.is_system_absolute()) 275 return source.value(); 276 return RebaseSourceAbsolutePath(source.value(), 277 settings->build_settings()->build_dir()); 278 279 case Subrange::NAME_PART: 280 return FindFilenameNoExtension(&source.value()).as_string(); 281 282 case Subrange::FILE_PART: 283 return source.GetName(); 284 285 case Subrange::SOURCE_DIR: 286 if (source.is_system_absolute()) 287 return DirectoryWithNoLastSlash(source.GetDir()); 288 return RebaseSourceAbsolutePath( 289 DirectoryWithNoLastSlash(source.GetDir()), 290 settings->build_settings()->build_dir()); 291 292 case Subrange::ROOT_RELATIVE_DIR: 293 if (source.is_system_absolute()) 294 return DirectoryWithNoLastSlash(source.GetDir()); 295 return RebaseSourceAbsolutePath( 296 DirectoryWithNoLastSlash(source.GetDir()), SourceDir("//")); 297 298 case Subrange::SOURCE_GEN_DIR: 299 return RebaseSourceAbsolutePath( 300 DirectoryWithNoLastSlash( 301 GetGenDirForSourceDir(settings, source.GetDir())), 302 settings->build_settings()->build_dir()); 303 304 case Subrange::SOURCE_OUT_DIR: 305 return RebaseSourceAbsolutePath( 306 DirectoryWithNoLastSlash( 307 GetOutputDirForSourceDir(settings, source.GetDir())), 308 settings->build_settings()->build_dir()); 309 310 default: 311 NOTREACHED(); 312 } 313 return std::string(); 314 } 315 316 void FileTemplate::ParseInput(const Value& value, Err* err) { 317 switch (value.type()) { 318 case Value::STRING: 319 ParseOneTemplateString(value.string_value()); 320 break; 321 case Value::LIST: 322 for (size_t i = 0; i < value.list_value().size(); i++) { 323 if (!value.list_value()[i].VerifyTypeIs(Value::STRING, err)) 324 return; 325 ParseOneTemplateString(value.list_value()[i].string_value()); 326 } 327 break; 328 default: 329 *err = Err(value, "File template must be a string or list.", 330 "A sarcastic comment about your skills goes here."); 331 } 332 } 333 334 void FileTemplate::ParseOneTemplateString(const std::string& str) { 335 templates_.container().resize(templates_.container().size() + 1); 336 Template& t = templates_[templates_.container().size() - 1]; 337 338 size_t cur = 0; 339 while (true) { 340 size_t next = str.find("{{", cur); 341 342 // Pick up everything from the previous spot to here as a literal. 343 if (next == std::string::npos) { 344 if (cur != str.size()) 345 t.container().push_back(Subrange(Subrange::LITERAL, str.substr(cur))); 346 break; 347 } else if (next > cur) { 348 t.container().push_back( 349 Subrange(Subrange::LITERAL, str.substr(cur, next - cur))); 350 } 351 352 // Given the name of the string constant and enum for a template parameter, 353 // checks for it and stores it. Writing this as a function requires passing 354 // the entire state of this function as arguments, so this actually ends 355 // up being more clear. 356 #define IF_MATCH_THEN_STORE(const_name, enum_name) \ 357 if (str.compare(next, arraysize(const_name) - 1, const_name) == 0) { \ 358 t.container().push_back(Subrange(Subrange::enum_name)); \ 359 types_required_[Subrange::enum_name] = true; \ 360 has_substitutions_ = true; \ 361 cur = next + arraysize(const_name) - 1; \ 362 } 363 364 // Decode the template param. 365 IF_MATCH_THEN_STORE(kSource, SOURCE) 366 else IF_MATCH_THEN_STORE(kSourceNamePart, NAME_PART) 367 else IF_MATCH_THEN_STORE(kSourceFilePart, FILE_PART) 368 else IF_MATCH_THEN_STORE(kSourceDir, SOURCE_DIR) 369 else IF_MATCH_THEN_STORE(kRootRelDir, ROOT_RELATIVE_DIR) 370 else IF_MATCH_THEN_STORE(kSourceGenDir, SOURCE_GEN_DIR) 371 else IF_MATCH_THEN_STORE(kSourceOutDir, SOURCE_OUT_DIR) 372 else { 373 // If it's not a match, treat it like a one-char literal (this will be 374 // rare, so it's not worth the bother to add to the previous literal) so 375 // we can keep going. 376 t.container().push_back(Subrange(Subrange::LITERAL, "{")); 377 cur = next + 1; 378 } 379 380 #undef IF_MATCH_THEN_STORE 381 } 382 } 383