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/MemoryBuffer.h" 31 #include "llvm/Support/Path.h" 32 #include "llvm/Support/PrettyStackTrace.h" 33 #include "llvm/Support/Process.h" 34 #include "llvm/Support/Program.h" 35 #include "llvm/Support/Signals.h" 36 #include "llvm/Support/StringSaver.h" 37 #include "llvm/Support/raw_ostream.h" 38 39 #include <algorithm> 40 #include <system_error> 41 42 using namespace llvm; 43 using namespace llvm::rc; 44 45 namespace { 46 47 // Input options tables. 48 49 enum ID { 50 OPT_INVALID = 0, // This is not a correct option ID. 51 #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ 52 HELPTEXT, METAVAR, VALUES) \ 53 OPT_##ID, 54 #include "Opts.inc" 55 #undef OPTION 56 }; 57 58 #define PREFIX(NAME, VALUE) const char *const NAME[] = VALUE; 59 #include "Opts.inc" 60 #undef PREFIX 61 62 const opt::OptTable::Info InfoTable[] = { 63 #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ 64 HELPTEXT, METAVAR, VALUES) \ 65 { \ 66 PREFIX, NAME, HELPTEXT, \ 67 METAVAR, OPT_##ID, opt::Option::KIND##Class, \ 68 PARAM, FLAGS, OPT_##GROUP, \ 69 OPT_##ALIAS, ALIASARGS, VALUES}, 70 #include "Opts.inc" 71 #undef OPTION 72 }; 73 74 class RcOptTable : public opt::OptTable { 75 public: 76 RcOptTable() : OptTable(InfoTable, /* IgnoreCase = */ true) {} 77 }; 78 79 enum Windres_ID { 80 WINDRES_INVALID = 0, // This is not a correct option ID. 81 #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ 82 HELPTEXT, METAVAR, VALUES) \ 83 WINDRES_##ID, 84 #include "WindresOpts.inc" 85 #undef OPTION 86 }; 87 88 #define PREFIX(NAME, VALUE) const char *const WINDRES_##NAME[] = VALUE; 89 #include "WindresOpts.inc" 90 #undef PREFIX 91 92 const opt::OptTable::Info WindresInfoTable[] = { 93 #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ 94 HELPTEXT, METAVAR, VALUES) \ 95 { \ 96 WINDRES_##PREFIX, NAME, HELPTEXT, \ 97 METAVAR, WINDRES_##ID, opt::Option::KIND##Class, \ 98 PARAM, FLAGS, WINDRES_##GROUP, \ 99 WINDRES_##ALIAS, ALIASARGS, VALUES}, 100 #include "WindresOpts.inc" 101 #undef OPTION 102 }; 103 104 class WindresOptTable : public opt::OptTable { 105 public: 106 WindresOptTable() : OptTable(WindresInfoTable, /* IgnoreCase = */ false) {} 107 }; 108 109 static ExitOnError ExitOnErr; 110 static FileRemover TempPreprocFile; 111 static FileRemover TempResFile; 112 113 [[noreturn]] static void fatalError(const Twine &Message) { 114 errs() << Message << "\n"; 115 exit(1); 116 } 117 118 std::string createTempFile(const Twine &Prefix, StringRef Suffix) { 119 std::error_code EC; 120 SmallString<128> FileName; 121 if ((EC = sys::fs::createTemporaryFile(Prefix, Suffix, FileName))) 122 fatalError("Unable to create temp file: " + EC.message()); 123 return static_cast<std::string>(FileName); 124 } 125 126 ErrorOr<std::string> findClang(const char *Argv0, StringRef Triple) { 127 StringRef Parent = llvm::sys::path::parent_path(Argv0); 128 ErrorOr<std::string> Path = std::error_code(); 129 std::string TargetClang = (Triple + "-clang").str(); 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 : {TargetClang.c_str(), "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 bool isUsableArch(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 true; 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 with them we need to set a 161 // different default arch. 162 return false; 163 } 164 } 165 166 Triple::ArchType getDefaultFallbackArch() { 167 return Triple::x86_64; 168 } 169 170 std::string getClangClTriple() { 171 Triple T(sys::getDefaultTargetTriple()); 172 if (!isUsableArch(T.getArch())) 173 T.setArch(getDefaultFallbackArch()); 174 T.setOS(Triple::Win32); 175 T.setVendor(Triple::PC); 176 T.setEnvironment(Triple::MSVC); 177 T.setObjectFormat(Triple::COFF); 178 return T.str(); 179 } 180 181 std::string getMingwTriple() { 182 Triple T(sys::getDefaultTargetTriple()); 183 if (!isUsableArch(T.getArch())) 184 T.setArch(getDefaultFallbackArch()); 185 if (T.isWindowsGNUEnvironment()) 186 return T.str(); 187 // Write out the literal form of the vendor/env here, instead of 188 // constructing them with enum values (which end up with them in 189 // normalized form). The literal form of the triple can matter for 190 // finding include files. 191 return (Twine(T.getArchName()) + "-w64-mingw32").str(); 192 } 193 194 enum Format { Rc, Res, Coff, Unknown }; 195 196 struct RcOptions { 197 bool Preprocess = true; 198 bool PrintCmdAndExit = false; 199 std::string Triple; 200 std::vector<std::string> PreprocessCmd; 201 std::vector<std::string> PreprocessArgs; 202 203 std::string InputFile; 204 Format InputFormat = Rc; 205 std::string OutputFile; 206 Format OutputFormat = Res; 207 208 bool BeVerbose = false; 209 WriterParams Params; 210 bool AppendNull = false; 211 bool IsDryRun = false; 212 // Set the default language; choose en-US arbitrarily. 213 unsigned LangId = (/*PrimaryLangId*/ 0x09) | (/*SubLangId*/ 0x01 << 10); 214 }; 215 216 bool preprocess(StringRef Src, StringRef Dst, const RcOptions &Opts, 217 const char *Argv0) { 218 std::string Clang; 219 if (Opts.PrintCmdAndExit) { 220 Clang = "clang"; 221 } else { 222 ErrorOr<std::string> ClangOrErr = findClang(Argv0, Opts.Triple); 223 if (ClangOrErr) { 224 Clang = *ClangOrErr; 225 } else { 226 errs() << "llvm-rc: Unable to find clang, skipping preprocessing." 227 << "\n"; 228 errs() << "Pass -no-cpp to disable preprocessing. This will be an error " 229 "in the future." 230 << "\n"; 231 return false; 232 } 233 } 234 235 SmallVector<StringRef, 8> Args = { 236 Clang, "--driver-mode=gcc", "-target", Opts.Triple, "-E", 237 "-xc", "-DRC_INVOKED"}; 238 if (!Opts.PreprocessCmd.empty()) { 239 Args.clear(); 240 for (const auto &S : Opts.PreprocessCmd) 241 Args.push_back(S); 242 } 243 Args.push_back(Src); 244 Args.push_back("-o"); 245 Args.push_back(Dst); 246 for (const auto &S : Opts.PreprocessArgs) 247 Args.push_back(S); 248 if (Opts.PrintCmdAndExit || Opts.BeVerbose) { 249 for (const auto &A : Args) { 250 outs() << " "; 251 sys::printArg(outs(), A, Opts.PrintCmdAndExit); 252 } 253 outs() << "\n"; 254 if (Opts.PrintCmdAndExit) 255 exit(0); 256 } 257 // The llvm Support classes don't handle reading from stdout of a child 258 // process; otherwise we could avoid using a temp file. 259 int Res = sys::ExecuteAndWait(Clang, Args); 260 if (Res) { 261 fatalError("llvm-rc: Preprocessing failed."); 262 } 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 (!ProgName.consume_back_insensitive("windres")) 273 return std::make_pair<bool, std::string>(false, ""); 274 ProgName.consume_back_insensitive("llvm-"); 275 ProgName.consume_back_insensitive("-"); 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(0, Opts.Params.CodePage)) 480 fatalError("Invalid code page: " + 481 InputArgs.getLastArgValue(WINDRES_codepage)); 482 } 483 if (InputArgs.hasArg(WINDRES_language)) { 484 StringRef Val = InputArgs.getLastArgValue(WINDRES_language); 485 Val.consume_front_insensitive("0x"); 486 if (Val.getAsInteger(16, Opts.LangId)) 487 fatalError("Invalid language id: " + 488 InputArgs.getLastArgValue(WINDRES_language)); 489 } 490 491 Opts.BeVerbose = InputArgs.hasArg(WINDRES_verbose); 492 493 return Opts; 494 } 495 496 RcOptions parseRcOptions(ArrayRef<const char *> ArgsArr, 497 ArrayRef<const char *> InputArgsArray) { 498 RcOptTable T; 499 RcOptions Opts; 500 unsigned MAI, MAC; 501 opt::InputArgList InputArgs = T.ParseArgs(ArgsArr, MAI, MAC); 502 503 // The tool prints nothing when invoked with no command-line arguments. 504 if (InputArgs.hasArg(OPT_help)) { 505 T.printHelp(outs(), "rc [options] file...", "Resource Converter", false); 506 exit(0); 507 } 508 509 std::vector<std::string> InArgsInfo = InputArgs.getAllArgValues(OPT_INPUT); 510 InArgsInfo.insert(InArgsInfo.end(), InputArgsArray.begin(), 511 InputArgsArray.end()); 512 if (InArgsInfo.size() != 1) { 513 fatalError("Exactly one input file should be provided."); 514 } 515 516 Opts.PrintCmdAndExit = InputArgs.hasArg(OPT__HASH_HASH_HASH); 517 Opts.Triple = getClangClTriple(); 518 for (const auto *Arg : 519 InputArgs.filtered(OPT_includepath, OPT_define, OPT_undef)) { 520 switch (Arg->getOption().getID()) { 521 case OPT_includepath: 522 Opts.PreprocessArgs.push_back("-I"); 523 break; 524 case OPT_define: 525 Opts.PreprocessArgs.push_back("-D"); 526 break; 527 case OPT_undef: 528 Opts.PreprocessArgs.push_back("-U"); 529 break; 530 } 531 Opts.PreprocessArgs.push_back(Arg->getValue()); 532 } 533 534 Opts.InputFile = InArgsInfo[0]; 535 Opts.BeVerbose = InputArgs.hasArg(OPT_verbose); 536 Opts.Preprocess = !InputArgs.hasArg(OPT_no_preprocess); 537 Opts.Params.Include = InputArgs.getAllArgValues(OPT_includepath); 538 Opts.Params.NoInclude = InputArgs.hasArg(OPT_noinclude); 539 if (Opts.Params.NoInclude) { 540 // Clear the INLCUDE variable for the external preprocessor 541 #ifdef _WIN32 542 ::_putenv("INCLUDE="); 543 #else 544 ::unsetenv("INCLUDE"); 545 #endif 546 } 547 if (InputArgs.hasArg(OPT_codepage)) { 548 if (InputArgs.getLastArgValue(OPT_codepage) 549 .getAsInteger(10, Opts.Params.CodePage)) 550 fatalError("Invalid code page: " + 551 InputArgs.getLastArgValue(OPT_codepage)); 552 } 553 Opts.IsDryRun = InputArgs.hasArg(OPT_dry_run); 554 auto OutArgsInfo = InputArgs.getAllArgValues(OPT_fileout); 555 if (OutArgsInfo.empty()) { 556 SmallString<128> OutputFile(Opts.InputFile); 557 llvm::sys::fs::make_absolute(OutputFile); 558 llvm::sys::path::replace_extension(OutputFile, "res"); 559 OutArgsInfo.push_back(std::string(OutputFile.str())); 560 } 561 if (!Opts.IsDryRun) { 562 if (OutArgsInfo.size() != 1) 563 fatalError( 564 "No more than one output file should be provided (using /FO flag)."); 565 Opts.OutputFile = OutArgsInfo[0]; 566 } 567 Opts.AppendNull = InputArgs.hasArg(OPT_add_null); 568 if (InputArgs.hasArg(OPT_lang_id)) { 569 StringRef Val = InputArgs.getLastArgValue(OPT_lang_id); 570 Val.consume_front_insensitive("0x"); 571 if (Val.getAsInteger(16, Opts.LangId)) 572 fatalError("Invalid language id: " + 573 InputArgs.getLastArgValue(OPT_lang_id)); 574 } 575 return Opts; 576 } 577 578 RcOptions getOptions(const char *Argv0, ArrayRef<const char *> ArgsArr, 579 ArrayRef<const char *> InputArgs) { 580 std::string Prefix; 581 bool IsWindres; 582 std::tie(IsWindres, Prefix) = isWindres(Argv0); 583 if (IsWindres) 584 return parseWindresOptions(ArgsArr, InputArgs, Prefix); 585 else 586 return parseRcOptions(ArgsArr, InputArgs); 587 } 588 589 void doRc(std::string Src, std::string Dest, RcOptions &Opts, 590 const char *Argv0) { 591 std::string PreprocessedFile = Src; 592 if (Opts.Preprocess) { 593 std::string OutFile = createTempFile("preproc", "rc"); 594 TempPreprocFile.setFile(OutFile); 595 if (preprocess(Src, OutFile, Opts, Argv0)) 596 PreprocessedFile = OutFile; 597 } 598 599 // Read and tokenize the input file. 600 ErrorOr<std::unique_ptr<MemoryBuffer>> File = 601 MemoryBuffer::getFile(PreprocessedFile); 602 if (!File) { 603 fatalError("Error opening file '" + Twine(PreprocessedFile) + 604 "': " + File.getError().message()); 605 } 606 607 std::unique_ptr<MemoryBuffer> FileContents = std::move(*File); 608 StringRef Contents = FileContents->getBuffer(); 609 610 std::string FilteredContents = filterCppOutput(Contents); 611 std::vector<RCToken> Tokens = ExitOnErr(tokenizeRC(FilteredContents)); 612 613 if (Opts.BeVerbose) { 614 const Twine TokenNames[] = { 615 #define TOKEN(Name) #Name, 616 #define SHORT_TOKEN(Name, Ch) #Name, 617 #include "ResourceScriptTokenList.def" 618 }; 619 620 for (const RCToken &Token : Tokens) { 621 outs() << TokenNames[static_cast<int>(Token.kind())] << ": " 622 << Token.value(); 623 if (Token.kind() == RCToken::Kind::Int) 624 outs() << "; int value = " << Token.intValue(); 625 626 outs() << "\n"; 627 } 628 } 629 630 WriterParams &Params = Opts.Params; 631 SmallString<128> InputFile(Src); 632 llvm::sys::fs::make_absolute(InputFile); 633 Params.InputFilePath = InputFile; 634 635 switch (Params.CodePage) { 636 case CpAcp: 637 case CpWin1252: 638 case CpUtf8: 639 break; 640 default: 641 fatalError("Unsupported code page, only 0, 1252 and 65001 are supported!"); 642 } 643 644 std::unique_ptr<ResourceFileWriter> Visitor; 645 646 if (!Opts.IsDryRun) { 647 std::error_code EC; 648 auto FOut = std::make_unique<raw_fd_ostream>( 649 Dest, EC, sys::fs::FA_Read | sys::fs::FA_Write); 650 if (EC) 651 fatalError("Error opening output file '" + Dest + "': " + EC.message()); 652 Visitor = std::make_unique<ResourceFileWriter>(Params, std::move(FOut)); 653 Visitor->AppendNull = Opts.AppendNull; 654 655 ExitOnErr(NullResource().visit(Visitor.get())); 656 657 unsigned PrimaryLangId = Opts.LangId & 0x3ff; 658 unsigned SubLangId = Opts.LangId >> 10; 659 ExitOnErr(LanguageResource(PrimaryLangId, SubLangId).visit(Visitor.get())); 660 } 661 662 rc::RCParser Parser{std::move(Tokens)}; 663 while (!Parser.isEof()) { 664 auto Resource = ExitOnErr(Parser.parseSingleResource()); 665 if (Opts.BeVerbose) 666 Resource->log(outs()); 667 if (!Opts.IsDryRun) 668 ExitOnErr(Resource->visit(Visitor.get())); 669 } 670 671 // STRINGTABLE resources come at the very end. 672 if (!Opts.IsDryRun) 673 ExitOnErr(Visitor->dumpAllStringTables()); 674 } 675 676 void doCvtres(std::string Src, std::string Dest, std::string TargetTriple) { 677 object::WindowsResourceParser Parser; 678 679 ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr = 680 MemoryBuffer::getFile(Src); 681 if (!BufferOrErr) 682 fatalError("Error opening file '" + Twine(Src) + 683 "': " + BufferOrErr.getError().message()); 684 std::unique_ptr<MemoryBuffer> &Buffer = BufferOrErr.get(); 685 std::unique_ptr<object::WindowsResource> Binary = 686 ExitOnErr(object::WindowsResource::createWindowsResource( 687 Buffer->getMemBufferRef())); 688 689 std::vector<std::string> Duplicates; 690 ExitOnErr(Parser.parse(Binary.get(), Duplicates)); 691 for (const auto &DupeDiag : Duplicates) 692 fatalError("Duplicate resources: " + DupeDiag); 693 694 Triple T(TargetTriple); 695 COFF::MachineTypes MachineType; 696 switch (T.getArch()) { 697 case Triple::x86: 698 MachineType = COFF::IMAGE_FILE_MACHINE_I386; 699 break; 700 case Triple::x86_64: 701 MachineType = COFF::IMAGE_FILE_MACHINE_AMD64; 702 break; 703 case Triple::arm: 704 case Triple::thumb: 705 MachineType = COFF::IMAGE_FILE_MACHINE_ARMNT; 706 break; 707 case Triple::aarch64: 708 MachineType = COFF::IMAGE_FILE_MACHINE_ARM64; 709 break; 710 default: 711 fatalError("Unsupported architecture in target '" + Twine(TargetTriple) + 712 "'"); 713 } 714 715 std::unique_ptr<MemoryBuffer> OutputBuffer = 716 ExitOnErr(object::writeWindowsResourceCOFF(MachineType, Parser, 717 /*DateTimeStamp*/ 0)); 718 std::unique_ptr<FileOutputBuffer> FileBuffer = 719 ExitOnErr(FileOutputBuffer::create(Dest, OutputBuffer->getBufferSize())); 720 std::copy(OutputBuffer->getBufferStart(), OutputBuffer->getBufferEnd(), 721 FileBuffer->getBufferStart()); 722 ExitOnErr(FileBuffer->commit()); 723 } 724 725 } // anonymous namespace 726 727 int main(int Argc, const char **Argv) { 728 InitLLVM X(Argc, Argv); 729 ExitOnErr.setBanner("llvm-rc: "); 730 731 const char **DashDash = std::find_if( 732 Argv + 1, Argv + Argc, [](StringRef Str) { return Str == "--"; }); 733 ArrayRef<const char *> ArgsArr = makeArrayRef(Argv + 1, DashDash); 734 ArrayRef<const char *> FileArgsArr; 735 if (DashDash != Argv + Argc) 736 FileArgsArr = makeArrayRef(DashDash + 1, Argv + Argc); 737 738 RcOptions Opts = getOptions(Argv[0], ArgsArr, FileArgsArr); 739 740 std::string ResFile = Opts.OutputFile; 741 if (Opts.InputFormat == Rc) { 742 if (Opts.OutputFormat == Coff) { 743 ResFile = createTempFile("rc", "res"); 744 TempResFile.setFile(ResFile); 745 } 746 doRc(Opts.InputFile, ResFile, Opts, Argv[0]); 747 } else { 748 ResFile = Opts.InputFile; 749 } 750 if (Opts.OutputFormat == Coff) { 751 doCvtres(ResFile, Opts.OutputFile, Opts.Triple); 752 } 753 754 return 0; 755 } 756