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 19 const char kSourceExpansion_Help[] = 20 "How Source Expansion Works\n" 21 "\n" 22 " Source expansion is used for the custom script and copy target types\n" 23 " to map source file names to output file names or arguments.\n" 24 "\n" 25 " To perform source expansion in the outputs, GN maps every entry in the\n" 26 " sources to every entry in the outputs list, producing the cross\n" 27 " product of all combinations, expanding placeholders (see below).\n" 28 "\n" 29 " Source expansion in the args works similarly, but performing the\n" 30 " placeholder substitution produces a different set of arguments for\n" 31 " each invocation of the script.\n" 32 "\n" 33 " If no placeholders are found, the outputs or args list will be treated\n" 34 " as a static list of literal file names that do not depend on the\n" 35 " sources.\n" 36 "\n" 37 " See \"gn help copy\" and \"gn help custom\" for more on how this is\n" 38 " applied.\n" 39 "\n" 40 "Placeholders\n" 41 "\n" 42 " {{source}}\n" 43 " The name of the source file relative to the root build output\n" 44 " directory (which is the current directory when running compilers\n" 45 " and scripts). This will generally be used for specifying inputs\n" 46 " to a script in the \"args\" variable.\n" 47 "\n" 48 " {{source_file_part}}\n" 49 " The file part of the source including the extension. For the\n" 50 " source \"foo/bar.txt\" the source file part will be \"bar.txt\".\n" 51 "\n" 52 " {{source_name_part}}\n" 53 " The filename part of the source file with no directory or\n" 54 " extension. This will generally be used for specifying a\n" 55 " transformation from a soruce file to a destination file with the\n" 56 " same name but different extension. For the source \"foo/bar.txt\"\n" 57 " the source name part will be \"bar\".\n" 58 "\n" 59 "Examples\n" 60 "\n" 61 " Non-varying outputs:\n" 62 " script(\"hardcoded_outputs\") {\n" 63 " sources = [ \"input1.idl\", \"input2.idl\" ]\n" 64 " outputs = [ \"$target_out_dir/output1.dat\",\n" 65 " \"$target_out_dir/output2.dat\" ]\n" 66 " }\n" 67 " The outputs in this case will be the two literal files given.\n" 68 "\n" 69 " Varying outputs:\n" 70 " script(\"varying_outputs\") {\n" 71 " sources = [ \"input1.idl\", \"input2.idl\" ]\n" 72 " outputs = [ \"$target_out_dir/{{source_name_part}}.h\",\n" 73 " \"$target_out_dir/{{source_name_part}}.cc\" ]\n" 74 " }\n" 75 " Performing source expansion will result in the following output names:\n" 76 " //out/Debug/obj/mydirectory/input1.h\n" 77 " //out/Debug/obj/mydirectory/input1.cc\n" 78 " //out/Debug/obj/mydirectory/input2.h\n" 79 " //out/Debug/obj/mydirectory/input2.cc\n"; 80 81 FileTemplate::FileTemplate(const Value& t, Err* err) 82 : has_substitutions_(false) { 83 std::fill(types_required_, &types_required_[Subrange::NUM_TYPES], false); 84 ParseInput(t, err); 85 } 86 87 FileTemplate::FileTemplate(const std::vector<std::string>& t) 88 : has_substitutions_(false) { 89 std::fill(types_required_, &types_required_[Subrange::NUM_TYPES], false); 90 for (size_t i = 0; i < t.size(); i++) 91 ParseOneTemplateString(t[i]); 92 } 93 94 FileTemplate::~FileTemplate() { 95 } 96 97 // static 98 FileTemplate FileTemplate::GetForTargetOutputs(const Target* target) { 99 const Target::FileList& outputs = target->script_values().outputs(); 100 std::vector<std::string> output_template_args; 101 for (size_t i = 0; i < outputs.size(); i++) 102 output_template_args.push_back(outputs[i].value()); 103 return FileTemplate(output_template_args); 104 } 105 106 bool FileTemplate::IsTypeUsed(Subrange::Type type) const { 107 DCHECK(type > Subrange::LITERAL && type < Subrange::NUM_TYPES); 108 return types_required_[type]; 109 } 110 111 void FileTemplate::Apply(const Value& sources, 112 const ParseNode* origin, 113 std::vector<Value>* dest, 114 Err* err) const { 115 if (!sources.VerifyTypeIs(Value::LIST, err)) 116 return; 117 dest->reserve(sources.list_value().size() * templates_.container().size()); 118 119 // Temporary holding place, allocate outside to re-use- buffer. 120 std::vector<std::string> string_output; 121 122 const std::vector<Value>& sources_list = sources.list_value(); 123 for (size_t i = 0; i < sources_list.size(); i++) { 124 if (!sources_list[i].VerifyTypeIs(Value::STRING, err)) 125 return; 126 127 ApplyString(sources_list[i].string_value(), &string_output); 128 for (size_t out_i = 0; out_i < string_output.size(); out_i++) 129 dest->push_back(Value(origin, string_output[i])); 130 } 131 } 132 133 void FileTemplate::ApplyString(const std::string& str, 134 std::vector<std::string>* output) const { 135 // Compute all substitutions needed so we can just do substitutions below. 136 // We skip the LITERAL one since that varies each time. 137 std::string subst[Subrange::NUM_TYPES]; 138 for (int i = 1; i < Subrange::NUM_TYPES; i++) { 139 if (types_required_[i]) 140 subst[i] = GetSubstitution(str, static_cast<Subrange::Type>(i)); 141 } 142 143 output->resize(templates_.container().size()); 144 for (size_t template_i = 0; 145 template_i < templates_.container().size(); template_i++) { 146 const Template& t = templates_[template_i]; 147 (*output)[template_i].clear(); 148 for (size_t subrange_i = 0; subrange_i < t.container().size(); 149 subrange_i++) { 150 if (t[subrange_i].type == Subrange::LITERAL) 151 (*output)[template_i].append(t[subrange_i].literal); 152 else 153 (*output)[template_i].append(subst[t[subrange_i].type]); 154 } 155 } 156 } 157 158 void FileTemplate::WriteWithNinjaExpansions(std::ostream& out) const { 159 EscapeOptions escape_options; 160 escape_options.mode = ESCAPE_NINJA_SHELL; 161 escape_options.inhibit_quoting = true; 162 163 for (size_t template_i = 0; 164 template_i < templates_.container().size(); template_i++) { 165 out << " "; // Separate args with spaces. 166 167 const Template& t = templates_[template_i]; 168 169 // Escape each subrange into a string. Since we're writing out Ninja 170 // variables, we can't quote the whole thing, so we write in pieces, only 171 // escaping the literals, and then quoting the whole thing at the end if 172 // necessary. 173 bool needs_quoting = false; 174 std::string item_str; 175 for (size_t subrange_i = 0; subrange_i < t.container().size(); 176 subrange_i++) { 177 if (t[subrange_i].type == Subrange::LITERAL) { 178 item_str.append(EscapeString(t[subrange_i].literal, escape_options, 179 &needs_quoting)); 180 } else { 181 // Don't escape this since we need to preserve the $. 182 item_str.append("${"); 183 item_str.append(GetNinjaVariableNameForType(t[subrange_i].type)); 184 item_str.append("}"); 185 } 186 } 187 188 if (needs_quoting) { 189 // Need to shell quote the whole string. 190 out << '"' << item_str << '"'; 191 } else { 192 out << item_str; 193 } 194 } 195 } 196 197 void FileTemplate::WriteNinjaVariablesForSubstitution( 198 std::ostream& out, 199 const std::string& source, 200 const EscapeOptions& escape_options) const { 201 for (int i = 1; i < Subrange::NUM_TYPES; i++) { 202 if (types_required_[i]) { 203 Subrange::Type type = static_cast<Subrange::Type>(i); 204 out << " " << GetNinjaVariableNameForType(type) << " = "; 205 EscapeStringToStream(out, GetSubstitution(source, type), escape_options); 206 out << std::endl; 207 } 208 } 209 } 210 211 // static 212 const char* FileTemplate::GetNinjaVariableNameForType(Subrange::Type type) { 213 switch (type) { 214 case Subrange::SOURCE: 215 return "source"; 216 case Subrange::NAME_PART: 217 return "source_name_part"; 218 case Subrange::FILE_PART: 219 return "source_file_part"; 220 default: 221 NOTREACHED(); 222 } 223 return ""; 224 } 225 226 // static 227 std::string FileTemplate::GetSubstitution(const std::string& source, 228 Subrange::Type type) { 229 switch (type) { 230 case Subrange::SOURCE: 231 return source; 232 case Subrange::NAME_PART: 233 return FindFilenameNoExtension(&source).as_string(); 234 case Subrange::FILE_PART: 235 return FindFilename(&source).as_string(); 236 default: 237 NOTREACHED(); 238 } 239 return std::string(); 240 } 241 242 void FileTemplate::ParseInput(const Value& value, Err* err) { 243 switch (value.type()) { 244 case Value::STRING: 245 ParseOneTemplateString(value.string_value()); 246 break; 247 case Value::LIST: 248 for (size_t i = 0; i < value.list_value().size(); i++) { 249 if (!value.list_value()[i].VerifyTypeIs(Value::STRING, err)) 250 return; 251 ParseOneTemplateString(value.list_value()[i].string_value()); 252 } 253 break; 254 default: 255 *err = Err(value, "File template must be a string or list.", 256 "A sarcastic comment about your skills goes here."); 257 } 258 } 259 260 void FileTemplate::ParseOneTemplateString(const std::string& str) { 261 templates_.container().resize(templates_.container().size() + 1); 262 Template& t = templates_[templates_.container().size() - 1]; 263 264 size_t cur = 0; 265 while (true) { 266 size_t next = str.find("{{", cur); 267 268 // Pick up everything from the previous spot to here as a literal. 269 if (next == std::string::npos) { 270 if (cur != str.size()) 271 t.container().push_back(Subrange(Subrange::LITERAL, str.substr(cur))); 272 break; 273 } else if (next > cur) { 274 t.container().push_back( 275 Subrange(Subrange::LITERAL, str.substr(cur, next - cur))); 276 } 277 278 // Decode the template param. 279 if (str.compare(next, arraysize(kSource) - 1, kSource) == 0) { 280 t.container().push_back(Subrange(Subrange::SOURCE)); 281 types_required_[Subrange::SOURCE] = true; 282 has_substitutions_ = true; 283 cur = next + arraysize(kSource) - 1; 284 } else if (str.compare(next, arraysize(kSourceNamePart) - 1, 285 kSourceNamePart) == 0) { 286 t.container().push_back(Subrange(Subrange::NAME_PART)); 287 types_required_[Subrange::NAME_PART] = true; 288 has_substitutions_ = true; 289 cur = next + arraysize(kSourceNamePart) - 1; 290 } else if (str.compare(next, arraysize(kSourceFilePart) - 1, 291 kSourceFilePart) == 0) { 292 t.container().push_back(Subrange(Subrange::FILE_PART)); 293 types_required_[Subrange::FILE_PART] = true; 294 has_substitutions_ = true; 295 cur = next + arraysize(kSourceFilePart) - 1; 296 } else { 297 // If it's not a match, treat it like a one-char literal (this will be 298 // rare, so it's not worth the bother to add to the previous literal) so 299 // we can keep going. 300 t.container().push_back(Subrange(Subrange::LITERAL, "{")); 301 cur = next + 1; 302 } 303 } 304 } 305