1 //===-- llvm-rc.cpp - Compile .rc scripts into .res -------------*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 // Compile .rc scripts into .res files. This is intended to be a 10 // platform-independent port of Microsoft's rc.exe tool. 11 // 12 //===----------------------------------------------------------------------===// 13 14 #include "ResourceFileWriter.h" 15 #include "ResourceScriptCppFilter.h" 16 #include "ResourceScriptParser.h" 17 #include "ResourceScriptStmt.h" 18 #include "ResourceScriptToken.h" 19 20 #include "llvm/ADT/Triple.h" 21 #include "llvm/Object/WindowsResource.h" 22 #include "llvm/Option/Arg.h" 23 #include "llvm/Option/ArgList.h" 24 #include "llvm/Support/CommandLine.h" 25 #include "llvm/Support/Error.h" 26 #include "llvm/Support/FileSystem.h" 27 #include "llvm/Support/FileUtilities.h" 28 #include "llvm/Support/Host.h" 29 #include "llvm/Support/InitLLVM.h" 30 #include "llvm/Support/ManagedStatic.h" 31 #include "llvm/Support/MemoryBuffer.h" 32 #include "llvm/Support/Path.h" 33 #include "llvm/Support/PrettyStackTrace.h" 34 #include "llvm/Support/Process.h" 35 #include "llvm/Support/Program.h" 36 #include "llvm/Support/Signals.h" 37 #include "llvm/Support/StringSaver.h" 38 #include "llvm/Support/raw_ostream.h" 39 40 #include <algorithm> 41 #include <system_error> 42 43 using namespace llvm; 44 using namespace llvm::rc; 45 46 namespace { 47 48 // Input options tables. 49 50 enum ID { 51 OPT_INVALID = 0, // This is not a correct option ID. 52 #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ 53 HELPTEXT, METAVAR, VALUES) \ 54 OPT_##ID, 55 #include "Opts.inc" 56 #undef OPTION 57 }; 58 59 #define PREFIX(NAME, VALUE) const char *const NAME[] = VALUE; 60 #include "Opts.inc" 61 #undef PREFIX 62 63 static const opt::OptTable::Info InfoTable[] = { 64 #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ 65 HELPTEXT, METAVAR, VALUES) \ 66 { \ 67 PREFIX, NAME, HELPTEXT, \ 68 METAVAR, OPT_##ID, opt::Option::KIND##Class, \ 69 PARAM, FLAGS, OPT_##GROUP, \ 70 OPT_##ALIAS, ALIASARGS, VALUES}, 71 #include "Opts.inc" 72 #undef OPTION 73 }; 74 75 class RcOptTable : public opt::OptTable { 76 public: 77 RcOptTable() : OptTable(InfoTable, /* IgnoreCase = */ true) {} 78 }; 79 80 enum Windres_ID { 81 WINDRES_INVALID = 0, // This is not a correct option ID. 82 #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ 83 HELPTEXT, METAVAR, VALUES) \ 84 WINDRES_##ID, 85 #include "WindresOpts.inc" 86 #undef OPTION 87 }; 88 89 #define PREFIX(NAME, VALUE) const char *const WINDRES_##NAME[] = VALUE; 90 #include "WindresOpts.inc" 91 #undef PREFIX 92 93 static const opt::OptTable::Info WindresInfoTable[] = { 94 #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ 95 HELPTEXT, METAVAR, VALUES) \ 96 { \ 97 WINDRES_##PREFIX, NAME, HELPTEXT, \ 98 METAVAR, WINDRES_##ID, opt::Option::KIND##Class, \ 99 PARAM, FLAGS, WINDRES_##GROUP, \ 100 WINDRES_##ALIAS, ALIASARGS, VALUES}, 101 #include "WindresOpts.inc" 102 #undef OPTION 103 }; 104 105 class WindresOptTable : public opt::OptTable { 106 public: 107 WindresOptTable() : OptTable(WindresInfoTable, /* IgnoreCase = */ false) {} 108 }; 109 110 static ExitOnError ExitOnErr; 111 static FileRemover TempPreprocFile; 112 static FileRemover TempResFile; 113 114 LLVM_ATTRIBUTE_NORETURN static void fatalError(const Twine &Message) { 115 errs() << Message << "\n"; 116 exit(1); 117 } 118 119 std::string createTempFile(const Twine &Prefix, StringRef Suffix) { 120 std::error_code EC; 121 SmallString<128> FileName; 122 if ((EC = sys::fs::createTemporaryFile(Prefix, Suffix, FileName))) 123 fatalError("Unable to create temp file: " + EC.message()); 124 return static_cast<std::string>(FileName); 125 } 126 127 ErrorOr<std::string> findClang(const char *Argv0) { 128 StringRef Parent = llvm::sys::path::parent_path(Argv0); 129 ErrorOr<std::string> Path = std::error_code(); 130 if (!Parent.empty()) { 131 // First look for the tool with all potential names in the specific 132 // directory of Argv0, if known 133 for (const auto *Name : {"clang", "clang-cl"}) { 134 Path = sys::findProgramByName(Name, Parent); 135 if (Path) 136 return Path; 137 } 138 } 139 // If no parent directory known, or not found there, look everywhere in PATH 140 for (const auto *Name : {"clang", "clang-cl"}) { 141 Path = sys::findProgramByName(Name); 142 if (Path) 143 return Path; 144 } 145 return Path; 146 } 147 148 Triple::ArchType getDefaultArch(Triple::ArchType Arch) { 149 switch (Arch) { 150 case Triple::x86: 151 case Triple::x86_64: 152 case Triple::arm: 153 case Triple::thumb: 154 case Triple::aarch64: 155 // These work properly with the clang driver, setting the expected 156 // defines such as _WIN32 etc. 157 return Arch; 158 default: 159 // Other archs aren't set up for use with windows as target OS, (clang 160 // doesn't define e.g. _WIN32 etc), so set a reasonable default arch. 161 return Triple::x86_64; 162 } 163 } 164 165 std::string getClangClTriple() { 166 Triple T(sys::getDefaultTargetTriple()); 167 T.setArch(getDefaultArch(T.getArch())); 168 T.setOS(Triple::Win32); 169 T.setVendor(Triple::PC); 170 T.setEnvironment(Triple::MSVC); 171 T.setObjectFormat(Triple::COFF); 172 return T.str(); 173 } 174 175 std::string getMingwTriple() { 176 Triple T(sys::getDefaultTargetTriple()); 177 T.setArch(getDefaultArch(T.getArch())); 178 if (T.isWindowsGNUEnvironment()) 179 return T.str(); 180 // Write out the literal form of the vendor/env here, instead of 181 // constructing them with enum values (which end up with them in 182 // normalized form). The literal form of the triple can matter for 183 // finding include files. 184 return (Twine(T.getArchName()) + "-w64-mingw32").str(); 185 } 186 187 enum Format { Rc, Res, Coff, Unknown }; 188 189 struct RcOptions { 190 bool Preprocess = true; 191 bool PrintCmdAndExit = false; 192 std::string Triple; 193 std::vector<std::string> PreprocessCmd; 194 std::vector<std::string> PreprocessArgs; 195 196 std::string InputFile; 197 Format InputFormat = Rc; 198 std::string OutputFile; 199 Format OutputFormat = Res; 200 201 bool BeVerbose = false; 202 WriterParams Params; 203 bool AppendNull = false; 204 bool IsDryRun = false; 205 // Set the default language; choose en-US arbitrarily. 206 unsigned LangId = (/*PrimaryLangId*/ 0x09) | (/*SubLangId*/ 0x01 << 10); 207 }; 208 209 bool preprocess(StringRef Src, StringRef Dst, const RcOptions &Opts, 210 const char *Argv0) { 211 std::string Clang; 212 if (Opts.PrintCmdAndExit) { 213 Clang = "clang"; 214 } else { 215 ErrorOr<std::string> ClangOrErr = findClang(Argv0); 216 if (ClangOrErr) { 217 Clang = *ClangOrErr; 218 } else { 219 errs() << "llvm-rc: Unable to find clang, skipping preprocessing." 220 << "\n"; 221 errs() << "Pass -no-cpp to disable preprocessing. This will be an error " 222 "in the future." 223 << "\n"; 224 return false; 225 } 226 } 227 228 SmallVector<StringRef, 8> Args = { 229 Clang, "--driver-mode=gcc", "-target", Opts.Triple, "-E", 230 "-xc", "-DRC_INVOKED"}; 231 if (!Opts.PreprocessCmd.empty()) { 232 Args.clear(); 233 for (const auto &S : Opts.PreprocessCmd) 234 Args.push_back(S); 235 } 236 Args.push_back(Src); 237 Args.push_back("-o"); 238 Args.push_back(Dst); 239 for (const auto &S : Opts.PreprocessArgs) 240 Args.push_back(S); 241 if (Opts.PrintCmdAndExit || Opts.BeVerbose) { 242 for (const auto &A : Args) { 243 outs() << " "; 244 sys::printArg(outs(), A, Opts.PrintCmdAndExit); 245 } 246 outs() << "\n"; 247 if (Opts.PrintCmdAndExit) 248 exit(0); 249 } 250 // The llvm Support classes don't handle reading from stdout of a child 251 // process; otherwise we could avoid using a temp file. 252 int Res = sys::ExecuteAndWait(Clang, Args); 253 if (Res) { 254 fatalError("llvm-rc: Preprocessing failed."); 255 } 256 return true; 257 } 258 259 static bool consume_back_lower(StringRef &S, const char *Str) { 260 if (!S.endswith_lower(Str)) 261 return false; 262 S = S.drop_back(strlen(Str)); 263 return true; 264 } 265 266 static std::pair<bool, std::string> isWindres(llvm::StringRef Argv0) { 267 StringRef ProgName = llvm::sys::path::stem(Argv0); 268 // x86_64-w64-mingw32-windres -> x86_64-w64-mingw32, windres 269 // llvm-rc -> "", llvm-rc 270 // aarch64-w64-mingw32-llvm-windres-10.exe -> aarch64-w64-mingw32, llvm-windres 271 ProgName = ProgName.rtrim("0123456789.-"); 272 if (!consume_back_lower(ProgName, "windres")) 273 return std::make_pair<bool, std::string>(false, ""); 274 consume_back_lower(ProgName, "llvm-"); 275 consume_back_lower(ProgName, "-"); 276 return std::make_pair<bool, std::string>(true, ProgName.str()); 277 } 278 279 Format parseFormat(StringRef S) { 280 Format F = StringSwitch<Format>(S.lower()) 281 .Case("rc", Rc) 282 .Case("res", Res) 283 .Case("coff", Coff) 284 .Default(Unknown); 285 if (F == Unknown) 286 fatalError("Unable to parse '" + Twine(S) + "' as a format"); 287 return F; 288 } 289 290 void deduceFormat(Format &Dest, StringRef File) { 291 Format F = StringSwitch<Format>(sys::path::extension(File.lower())) 292 .Case(".rc", Rc) 293 .Case(".res", Res) 294 .Case(".o", Coff) 295 .Case(".obj", Coff) 296 .Default(Unknown); 297 if (F != Unknown) 298 Dest = F; 299 } 300 301 std::string unescape(StringRef S) { 302 std::string Out; 303 Out.reserve(S.size()); 304 for (int I = 0, E = S.size(); I < E; I++) { 305 if (S[I] == '\\') { 306 if (I + 1 < E) 307 Out.push_back(S[++I]); 308 else 309 fatalError("Unterminated escape"); 310 continue; 311 } 312 Out.push_back(S[I]); 313 } 314 return Out; 315 } 316 317 std::vector<std::string> unescapeSplit(StringRef S) { 318 std::vector<std::string> OutArgs; 319 std::string Out; 320 bool InQuote = false; 321 for (int I = 0, E = S.size(); I < E; I++) { 322 if (S[I] == '\\') { 323 if (I + 1 < E) 324 Out.push_back(S[++I]); 325 else 326 fatalError("Unterminated escape"); 327 continue; 328 } 329 if (S[I] == '"') { 330 InQuote = !InQuote; 331 continue; 332 } 333 if (S[I] == ' ' && !InQuote) { 334 OutArgs.push_back(Out); 335 Out.clear(); 336 continue; 337 } 338 Out.push_back(S[I]); 339 } 340 if (InQuote) 341 fatalError("Unterminated quote"); 342 if (!Out.empty()) 343 OutArgs.push_back(Out); 344 return OutArgs; 345 } 346 347 RcOptions parseWindresOptions(ArrayRef<const char *> ArgsArr, 348 ArrayRef<const char *> InputArgsArray, 349 std::string Prefix) { 350 WindresOptTable T; 351 RcOptions Opts; 352 unsigned MAI, MAC; 353 opt::InputArgList InputArgs = T.ParseArgs(ArgsArr, MAI, MAC); 354 355 // The tool prints nothing when invoked with no command-line arguments. 356 if (InputArgs.hasArg(WINDRES_help)) { 357 T.PrintHelp(outs(), "windres [options] file...", 358 "LLVM windres (GNU windres compatible)", false, true); 359 exit(0); 360 } 361 362 if (InputArgs.hasArg(WINDRES_version)) { 363 outs() << "llvm-windres, compatible with GNU windres\n"; 364 cl::PrintVersionMessage(); 365 exit(0); 366 } 367 368 std::vector<std::string> FileArgs = InputArgs.getAllArgValues(WINDRES_INPUT); 369 FileArgs.insert(FileArgs.end(), InputArgsArray.begin(), InputArgsArray.end()); 370 371 if (InputArgs.hasArg(WINDRES_input)) { 372 Opts.InputFile = InputArgs.getLastArgValue(WINDRES_input).str(); 373 } else if (!FileArgs.empty()) { 374 Opts.InputFile = FileArgs.front(); 375 FileArgs.erase(FileArgs.begin()); 376 } else { 377 // TODO: GNU windres takes input on stdin in this case. 378 fatalError("Missing input file"); 379 } 380 381 if (InputArgs.hasArg(WINDRES_output)) { 382 Opts.OutputFile = InputArgs.getLastArgValue(WINDRES_output).str(); 383 } else if (!FileArgs.empty()) { 384 Opts.OutputFile = FileArgs.front(); 385 FileArgs.erase(FileArgs.begin()); 386 } else { 387 // TODO: GNU windres writes output in rc form to stdout in this case. 388 fatalError("Missing output file"); 389 } 390 391 if (InputArgs.hasArg(WINDRES_input_format)) { 392 Opts.InputFormat = 393 parseFormat(InputArgs.getLastArgValue(WINDRES_input_format)); 394 } else { 395 deduceFormat(Opts.InputFormat, Opts.InputFile); 396 } 397 if (Opts.InputFormat == Coff) 398 fatalError("Unsupported input format"); 399 400 if (InputArgs.hasArg(WINDRES_output_format)) { 401 Opts.OutputFormat = 402 parseFormat(InputArgs.getLastArgValue(WINDRES_output_format)); 403 } else { 404 // The default in windres differs from the default in RcOptions 405 Opts.OutputFormat = Coff; 406 deduceFormat(Opts.OutputFormat, Opts.OutputFile); 407 } 408 if (Opts.OutputFormat == Rc) 409 fatalError("Unsupported output format"); 410 if (Opts.InputFormat == Opts.OutputFormat) { 411 outs() << "Nothing to do.\n"; 412 exit(0); 413 } 414 415 Opts.PrintCmdAndExit = InputArgs.hasArg(WINDRES__HASH_HASH_HASH); 416 Opts.Preprocess = !InputArgs.hasArg(WINDRES_no_preprocess); 417 Triple TT(Prefix); 418 if (InputArgs.hasArg(WINDRES_target)) { 419 StringRef Value = InputArgs.getLastArgValue(WINDRES_target); 420 if (Value == "pe-i386") 421 Opts.Triple = "i686-w64-mingw32"; 422 else if (Value == "pe-x86-64") 423 Opts.Triple = "x86_64-w64-mingw32"; 424 else 425 // Implicit extension; if the --target value isn't one of the known 426 // BFD targets, allow setting the full triple string via this instead. 427 Opts.Triple = Value.str(); 428 } else if (TT.getArch() != Triple::UnknownArch) 429 Opts.Triple = Prefix; 430 else 431 Opts.Triple = getMingwTriple(); 432 433 for (const auto *Arg : 434 InputArgs.filtered(WINDRES_include_dir, WINDRES_define, WINDRES_undef, 435 WINDRES_preprocessor_arg)) { 436 // GNU windres passes the arguments almost as-is on to popen() (it only 437 // backslash escapes spaces in the arguments), where a shell would 438 // unescape backslash escapes for quotes and similar. This means that 439 // when calling GNU windres, callers need to double escape chars like 440 // quotes, e.g. as -DSTRING=\\\"1.2.3\\\". 441 // 442 // Exactly how the arguments are interpreted depends on the platform 443 // though - but the cases where this matters (where callers would have 444 // done this double escaping) probably is confined to cases like these 445 // quoted string defines, and those happen to work the same across unix 446 // and windows. 447 std::string Unescaped = unescape(Arg->getValue()); 448 switch (Arg->getOption().getID()) { 449 case WINDRES_include_dir: 450 // Technically, these are handled the same way as e.g. defines, but 451 // the way we consistently unescape the unix way breaks windows paths 452 // with single backslashes. Alternatively, our unescape function would 453 // need to mimic the platform specific command line parsing/unescaping 454 // logic. 455 Opts.Params.Include.push_back(Arg->getValue()); 456 Opts.PreprocessArgs.push_back("-I"); 457 Opts.PreprocessArgs.push_back(Arg->getValue()); 458 break; 459 case WINDRES_define: 460 Opts.PreprocessArgs.push_back("-D"); 461 Opts.PreprocessArgs.push_back(Unescaped); 462 break; 463 case WINDRES_undef: 464 Opts.PreprocessArgs.push_back("-U"); 465 Opts.PreprocessArgs.push_back(Unescaped); 466 break; 467 case WINDRES_preprocessor_arg: 468 Opts.PreprocessArgs.push_back(Unescaped); 469 break; 470 } 471 } 472 if (InputArgs.hasArg(WINDRES_preprocessor)) 473 Opts.PreprocessCmd = 474 unescapeSplit(InputArgs.getLastArgValue(WINDRES_preprocessor)); 475 476 Opts.Params.CodePage = CpWin1252; // Different default 477 if (InputArgs.hasArg(WINDRES_codepage)) { 478 if (InputArgs.getLastArgValue(WINDRES_codepage) 479 .getAsInteger(10, Opts.Params.CodePage)) 480 fatalError("Invalid code page: " + 481 InputArgs.getLastArgValue(WINDRES_codepage)); 482 } 483 if (InputArgs.hasArg(WINDRES_language)) { 484 if (InputArgs.getLastArgValue(WINDRES_language) 485 .getAsInteger(16, Opts.LangId)) 486 fatalError("Invalid language id: " + 487 InputArgs.getLastArgValue(WINDRES_language)); 488 } 489 490 Opts.BeVerbose = InputArgs.hasArg(WINDRES_verbose); 491 492 return Opts; 493 } 494 495 RcOptions parseRcOptions(ArrayRef<const char *> ArgsArr, 496 ArrayRef<const char *> InputArgsArray) { 497 RcOptTable T; 498 RcOptions Opts; 499 unsigned MAI, MAC; 500 opt::InputArgList InputArgs = T.ParseArgs(ArgsArr, MAI, MAC); 501 502 // The tool prints nothing when invoked with no command-line arguments. 503 if (InputArgs.hasArg(OPT_help)) { 504 T.PrintHelp(outs(), "rc [options] file...", "Resource Converter", false); 505 exit(0); 506 } 507 508 std::vector<std::string> InArgsInfo = InputArgs.getAllArgValues(OPT_INPUT); 509 InArgsInfo.insert(InArgsInfo.end(), InputArgsArray.begin(), 510 InputArgsArray.end()); 511 if (InArgsInfo.size() != 1) { 512 fatalError("Exactly one input file should be provided."); 513 } 514 515 Opts.PrintCmdAndExit = InputArgs.hasArg(OPT__HASH_HASH_HASH); 516 Opts.Triple = getClangClTriple(); 517 for (const auto *Arg : 518 InputArgs.filtered(OPT_includepath, OPT_define, OPT_undef)) { 519 switch (Arg->getOption().getID()) { 520 case OPT_includepath: 521 Opts.PreprocessArgs.push_back("-I"); 522 break; 523 case OPT_define: 524 Opts.PreprocessArgs.push_back("-D"); 525 break; 526 case OPT_undef: 527 Opts.PreprocessArgs.push_back("-U"); 528 break; 529 } 530 Opts.PreprocessArgs.push_back(Arg->getValue()); 531 } 532 533 Opts.InputFile = InArgsInfo[0]; 534 Opts.BeVerbose = InputArgs.hasArg(OPT_verbose); 535 Opts.Preprocess = !InputArgs.hasArg(OPT_no_preprocess); 536 Opts.Params.Include = InputArgs.getAllArgValues(OPT_includepath); 537 Opts.Params.NoInclude = InputArgs.hasArg(OPT_noinclude); 538 if (Opts.Params.NoInclude) { 539 // Clear the INLCUDE variable for the external preprocessor 540 #ifdef _WIN32 541 ::_putenv("INCLUDE="); 542 #else 543 ::unsetenv("INCLUDE"); 544 #endif 545 } 546 if (InputArgs.hasArg(OPT_codepage)) { 547 if (InputArgs.getLastArgValue(OPT_codepage) 548 .getAsInteger(10, Opts.Params.CodePage)) 549 fatalError("Invalid code page: " + 550 InputArgs.getLastArgValue(OPT_codepage)); 551 } 552 Opts.IsDryRun = InputArgs.hasArg(OPT_dry_run); 553 auto OutArgsInfo = InputArgs.getAllArgValues(OPT_fileout); 554 if (OutArgsInfo.empty()) { 555 SmallString<128> OutputFile(Opts.InputFile); 556 llvm::sys::fs::make_absolute(OutputFile); 557 llvm::sys::path::replace_extension(OutputFile, "res"); 558 OutArgsInfo.push_back(std::string(OutputFile.str())); 559 } 560 if (!Opts.IsDryRun) { 561 if (OutArgsInfo.size() != 1) 562 fatalError( 563 "No more than one output file should be provided (using /FO flag)."); 564 Opts.OutputFile = OutArgsInfo[0]; 565 } 566 Opts.AppendNull = InputArgs.hasArg(OPT_add_null); 567 if (InputArgs.hasArg(OPT_lang_id)) { 568 if (InputArgs.getLastArgValue(OPT_lang_id).getAsInteger(16, Opts.LangId)) 569 fatalError("Invalid language id: " + 570 InputArgs.getLastArgValue(OPT_lang_id)); 571 } 572 return Opts; 573 } 574 575 RcOptions getOptions(const char *Argv0, ArrayRef<const char *> ArgsArr, 576 ArrayRef<const char *> InputArgs) { 577 std::string Prefix; 578 bool IsWindres; 579 std::tie(IsWindres, Prefix) = isWindres(Argv0); 580 if (IsWindres) 581 return parseWindresOptions(ArgsArr, InputArgs, Prefix); 582 else 583 return parseRcOptions(ArgsArr, InputArgs); 584 } 585 586 void doRc(std::string Src, std::string Dest, RcOptions &Opts, 587 const char *Argv0) { 588 std::string PreprocessedFile = Src; 589 if (Opts.Preprocess) { 590 std::string OutFile = createTempFile("preproc", "rc"); 591 TempPreprocFile.setFile(OutFile); 592 if (preprocess(Src, OutFile, Opts, Argv0)) 593 PreprocessedFile = OutFile; 594 } 595 596 // Read and tokenize the input file. 597 ErrorOr<std::unique_ptr<MemoryBuffer>> File = 598 MemoryBuffer::getFile(PreprocessedFile); 599 if (!File) { 600 fatalError("Error opening file '" + Twine(PreprocessedFile) + 601 "': " + File.getError().message()); 602 } 603 604 std::unique_ptr<MemoryBuffer> FileContents = std::move(*File); 605 StringRef Contents = FileContents->getBuffer(); 606 607 std::string FilteredContents = filterCppOutput(Contents); 608 std::vector<RCToken> Tokens = ExitOnErr(tokenizeRC(FilteredContents)); 609 610 if (Opts.BeVerbose) { 611 const Twine TokenNames[] = { 612 #define TOKEN(Name) #Name, 613 #define SHORT_TOKEN(Name, Ch) #Name, 614 #include "ResourceScriptTokenList.def" 615 }; 616 617 for (const RCToken &Token : Tokens) { 618 outs() << TokenNames[static_cast<int>(Token.kind())] << ": " 619 << Token.value(); 620 if (Token.kind() == RCToken::Kind::Int) 621 outs() << "; int value = " << Token.intValue(); 622 623 outs() << "\n"; 624 } 625 } 626 627 WriterParams &Params = Opts.Params; 628 SmallString<128> InputFile(Src); 629 llvm::sys::fs::make_absolute(InputFile); 630 Params.InputFilePath = InputFile; 631 632 switch (Params.CodePage) { 633 case CpAcp: 634 case CpWin1252: 635 case CpUtf8: 636 break; 637 default: 638 fatalError("Unsupported code page, only 0, 1252 and 65001 are supported!"); 639 } 640 641 std::unique_ptr<ResourceFileWriter> Visitor; 642 643 if (!Opts.IsDryRun) { 644 std::error_code EC; 645 auto FOut = std::make_unique<raw_fd_ostream>( 646 Dest, EC, sys::fs::FA_Read | sys::fs::FA_Write); 647 if (EC) 648 fatalError("Error opening output file '" + Dest + "': " + EC.message()); 649 Visitor = std::make_unique<ResourceFileWriter>(Params, std::move(FOut)); 650 Visitor->AppendNull = Opts.AppendNull; 651 652 ExitOnErr(NullResource().visit(Visitor.get())); 653 654 unsigned PrimaryLangId = Opts.LangId & 0x3ff; 655 unsigned SubLangId = Opts.LangId >> 10; 656 ExitOnErr(LanguageResource(PrimaryLangId, SubLangId).visit(Visitor.get())); 657 } 658 659 rc::RCParser Parser{std::move(Tokens)}; 660 while (!Parser.isEof()) { 661 auto Resource = ExitOnErr(Parser.parseSingleResource()); 662 if (Opts.BeVerbose) 663 Resource->log(outs()); 664 if (!Opts.IsDryRun) 665 ExitOnErr(Resource->visit(Visitor.get())); 666 } 667 668 // STRINGTABLE resources come at the very end. 669 if (!Opts.IsDryRun) 670 ExitOnErr(Visitor->dumpAllStringTables()); 671 } 672 673 void doCvtres(std::string Src, std::string Dest, std::string TargetTriple) { 674 object::WindowsResourceParser Parser; 675 676 ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr = 677 MemoryBuffer::getFile(Src); 678 if (!BufferOrErr) 679 fatalError("Error opening file '" + Twine(Src) + 680 "': " + BufferOrErr.getError().message()); 681 std::unique_ptr<MemoryBuffer> &Buffer = BufferOrErr.get(); 682 std::unique_ptr<object::WindowsResource> Binary = 683 ExitOnErr(object::WindowsResource::createWindowsResource( 684 Buffer->getMemBufferRef())); 685 686 std::vector<std::string> Duplicates; 687 ExitOnErr(Parser.parse(Binary.get(), Duplicates)); 688 for (const auto &DupeDiag : Duplicates) 689 fatalError("Duplicate resources: " + DupeDiag); 690 691 Triple T(TargetTriple); 692 COFF::MachineTypes MachineType; 693 switch (T.getArch()) { 694 case Triple::x86: 695 MachineType = COFF::IMAGE_FILE_MACHINE_I386; 696 break; 697 case Triple::x86_64: 698 MachineType = COFF::IMAGE_FILE_MACHINE_AMD64; 699 break; 700 case Triple::arm: 701 case Triple::thumb: 702 MachineType = COFF::IMAGE_FILE_MACHINE_ARMNT; 703 break; 704 case Triple::aarch64: 705 MachineType = COFF::IMAGE_FILE_MACHINE_ARM64; 706 break; 707 default: 708 fatalError("Unsupported architecture in target '" + Twine(TargetTriple) + 709 "'"); 710 } 711 712 std::unique_ptr<MemoryBuffer> OutputBuffer = 713 ExitOnErr(object::writeWindowsResourceCOFF(MachineType, Parser, 714 /*DateTimeStamp*/ 0)); 715 std::unique_ptr<FileOutputBuffer> FileBuffer = 716 ExitOnErr(FileOutputBuffer::create(Dest, OutputBuffer->getBufferSize())); 717 std::copy(OutputBuffer->getBufferStart(), OutputBuffer->getBufferEnd(), 718 FileBuffer->getBufferStart()); 719 ExitOnErr(FileBuffer->commit()); 720 } 721 722 } // anonymous namespace 723 724 int main(int Argc, const char **Argv) { 725 InitLLVM X(Argc, Argv); 726 ExitOnErr.setBanner("llvm-rc: "); 727 728 const char **DashDash = std::find_if( 729 Argv + 1, Argv + Argc, [](StringRef Str) { return Str == "--"; }); 730 ArrayRef<const char *> ArgsArr = makeArrayRef(Argv + 1, DashDash); 731 ArrayRef<const char *> FileArgsArr; 732 if (DashDash != Argv + Argc) 733 FileArgsArr = makeArrayRef(DashDash + 1, Argv + Argc); 734 735 RcOptions Opts = getOptions(Argv[0], ArgsArr, FileArgsArr); 736 737 std::string ResFile = Opts.OutputFile; 738 if (Opts.InputFormat == Rc) { 739 if (Opts.OutputFormat == Coff) { 740 ResFile = createTempFile("rc", "res"); 741 TempResFile.setFile(ResFile); 742 } 743 doRc(Opts.InputFile, ResFile, Opts, Argv[0]); 744 } else { 745 ResFile = Opts.InputFile; 746 } 747 if (Opts.OutputFormat == Coff) { 748 doCvtres(ResFile, Opts.OutputFile, Opts.Triple); 749 } 750 751 return 0; 752 } 753