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