770 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8//9// Compile .rc scripts into .res files. This is intended to be a10// 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 44using namespace llvm;45using namespace llvm::rc;46using namespace llvm::opt;47 48namespace {49 50// Input options tables.51 52enum 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 OPTION57};58 59namespace rc_opt {60#define OPTTABLE_STR_TABLE_CODE61#include "Opts.inc"62#undef OPTTABLE_STR_TABLE_CODE63 64#define OPTTABLE_PREFIXES_TABLE_CODE65#include "Opts.inc"66#undef OPTTABLE_PREFIXES_TABLE_CODE67 68static constexpr opt::OptTable::Info InfoTable[] = {69#define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__),70#include "Opts.inc"71#undef OPTION72};73} // namespace rc_opt74 75class RcOptTable : public opt::GenericOptTable {76public:77 RcOptTable()78 : GenericOptTable(rc_opt::OptionStrTable, rc_opt::OptionPrefixesTable,79 rc_opt::InfoTable,80 /* IgnoreCase = */ true) {}81};82 83enum 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 OPTION88};89 90namespace windres_opt {91#define OPTTABLE_STR_TABLE_CODE92#include "WindresOpts.inc"93#undef OPTTABLE_STR_TABLE_CODE94 95#define OPTTABLE_PREFIXES_TABLE_CODE96#include "WindresOpts.inc"97#undef OPTTABLE_PREFIXES_TABLE_CODE98 99static 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 OPTION104};105} // namespace windres_opt106 107class WindresOptTable : public opt::GenericOptTable {108public:109 WindresOptTable()110 : GenericOptTable(windres_opt::OptionStrTable,111 windres_opt::OptionPrefixesTable,112 windres_opt::InfoTable,113 /* IgnoreCase = */ false) {}114};115 116static ExitOnError ExitOnErr;117static FileRemover TempPreprocFile;118static FileRemover TempResFile;119 120[[noreturn]] static void fatalError(const Twine &Message) {121 errs() << Message << "\n";122 exit(1);123}124 125std::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 133ErrorOr<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 parent149 // directory and then in the argv[0] parent directory.150 // On Windows (but not Unix) argv[0] is overwritten with the eqiuvalent151 // 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 PATH159 for (const auto *Name : {"clang", "clang-cl"}) {160 Path = sys::findProgramByName(Name);161 if (Path)162 return Path;163 }164 return Path;165}166 167bool 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 expected175 // defines such as _WIN32 etc.176 return true;177 default:178 // Other archs aren't set up for use with windows as target OS, (clang179 // doesn't define e.g. _WIN32 etc), so with them we need to set a180 // different default arch.181 return false;182 }183}184 185Triple::ArchType getDefaultFallbackArch() {186 return Triple::x86_64;187}188 189std::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 200std::string getMingwTriple() {201 Triple T(sys::getDefaultTargetTriple());202 if (!isUsableArch(T.getArch()))203 T.setArch(getDefaultFallbackArch());204 if (T.isOSCygMing())205 return T.str();206 // Write out the literal form of the vendor/env here, instead of207 // constructing them with enum values (which end up with them in208 // normalized form). The literal form of the triple can matter for209 // finding include files.210 return (Twine(T.getArchName()) + "-w64-mingw32").str();211}212 213enum Format { Rc, Res, Coff, Unknown };214 215struct 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 236void 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 llvm::append_range(Args, Opts.PreprocessArgs);270 Args.push_back(Src);271 Args.push_back("-o");272 Args.push_back(Dst);273 if (Opts.PrintCmdAndExit || Opts.BeVerbose) {274 for (const auto &A : Args) {275 outs() << " ";276 sys::printArg(outs(), A, Opts.PrintCmdAndExit);277 }278 outs() << "\n";279 if (Opts.PrintCmdAndExit)280 exit(0);281 }282 // The llvm Support classes don't handle reading from stdout of a child283 // process; otherwise we could avoid using a temp file.284 std::string ErrMsg;285 int Res =286 sys::ExecuteAndWait(Args[0], Args, /*Env=*/std::nullopt, /*Redirects=*/{},287 /*SecondsToWait=*/0, /*MemoryLimit=*/0, &ErrMsg);288 if (Res) {289 if (!ErrMsg.empty())290 fatalError("llvm-rc: Preprocessing failed: " + ErrMsg);291 else292 fatalError("llvm-rc: Preprocessing failed.");293 }294}295 296static std::pair<bool, std::string> isWindres(llvm::StringRef Argv0) {297 StringRef ProgName = llvm::sys::path::stem(Argv0);298 // x86_64-w64-mingw32-windres -> x86_64-w64-mingw32, windres299 // llvm-rc -> "", llvm-rc300 // aarch64-w64-mingw32-llvm-windres-10.exe -> aarch64-w64-mingw32, llvm-windres301 ProgName = ProgName.rtrim("0123456789.-");302 if (!ProgName.consume_back_insensitive("windres"))303 return std::make_pair<bool, std::string>(false, "");304 ProgName.consume_back_insensitive("llvm-");305 ProgName.consume_back_insensitive("-");306 return std::make_pair<bool, std::string>(true, ProgName.str());307}308 309Format parseFormat(StringRef S) {310 Format F = StringSwitch<Format>(S.lower())311 .Case("rc", Rc)312 .Case("res", Res)313 .Case("coff", Coff)314 .Default(Unknown);315 if (F == Unknown)316 fatalError("Unable to parse '" + Twine(S) + "' as a format");317 return F;318}319 320void deduceFormat(Format &Dest, StringRef File) {321 Format F = StringSwitch<Format>(sys::path::extension(File.lower()))322 .Case(".rc", Rc)323 .Case(".res", Res)324 .Case(".o", Coff)325 .Case(".obj", Coff)326 .Default(Unknown);327 if (F != Unknown)328 Dest = F;329}330 331std::string unescape(StringRef S) {332 std::string Out;333 Out.reserve(S.size());334 for (int I = 0, E = S.size(); I < E; I++) {335 if (S[I] == '\\') {336 if (I + 1 < E)337 Out.push_back(S[++I]);338 else339 fatalError("Unterminated escape");340 continue;341 } else if (S[I] == '"') {342 // This eats an individual unescaped quote, like a shell would do.343 continue;344 }345 Out.push_back(S[I]);346 }347 return Out;348}349 350RcOptions parseWindresOptions(ArrayRef<const char *> ArgsArr,351 ArrayRef<const char *> InputArgsArray,352 std::string Prefix) {353 WindresOptTable T;354 RcOptions Opts;355 unsigned MAI, MAC;356 opt::InputArgList InputArgs = T.ParseArgs(ArgsArr, MAI, MAC);357 358 Opts.IsWindres = true;359 360 // The tool prints nothing when invoked with no command-line arguments.361 if (InputArgs.hasArg(WINDRES_help)) {362 T.printHelp(outs(), "windres [options] file...",363 "LLVM windres (GNU windres compatible)", false, true);364 exit(0);365 }366 367 if (InputArgs.hasArg(WINDRES_version)) {368 outs() << "llvm-windres, compatible with GNU windres\n";369 cl::PrintVersionMessage();370 exit(0);371 }372 373 std::vector<std::string> FileArgs = InputArgs.getAllArgValues(WINDRES_INPUT);374 llvm::append_range(FileArgs, InputArgsArray);375 376 if (InputArgs.hasArg(WINDRES_input)) {377 Opts.InputFile = InputArgs.getLastArgValue(WINDRES_input).str();378 } else if (!FileArgs.empty()) {379 Opts.InputFile = FileArgs.front();380 FileArgs.erase(FileArgs.begin());381 } else {382 // TODO: GNU windres takes input on stdin in this case.383 fatalError("Missing input file");384 }385 386 if (InputArgs.hasArg(WINDRES_output)) {387 Opts.OutputFile = InputArgs.getLastArgValue(WINDRES_output).str();388 } else if (!FileArgs.empty()) {389 Opts.OutputFile = FileArgs.front();390 FileArgs.erase(FileArgs.begin());391 } else {392 // TODO: GNU windres writes output in rc form to stdout in this case.393 fatalError("Missing output file");394 }395 396 if (InputArgs.hasArg(WINDRES_input_format)) {397 Opts.InputFormat =398 parseFormat(InputArgs.getLastArgValue(WINDRES_input_format));399 } else {400 deduceFormat(Opts.InputFormat, Opts.InputFile);401 }402 if (Opts.InputFormat == Coff)403 fatalError("Unsupported input format");404 405 if (InputArgs.hasArg(WINDRES_output_format)) {406 Opts.OutputFormat =407 parseFormat(InputArgs.getLastArgValue(WINDRES_output_format));408 } else {409 // The default in windres differs from the default in RcOptions410 Opts.OutputFormat = Coff;411 deduceFormat(Opts.OutputFormat, Opts.OutputFile);412 }413 if (Opts.OutputFormat == Rc)414 fatalError("Unsupported output format");415 if (Opts.InputFormat == Opts.OutputFormat) {416 outs() << "Nothing to do.\n";417 exit(0);418 }419 420 Opts.PrintCmdAndExit = InputArgs.hasArg(WINDRES__HASH_HASH_HASH);421 Opts.Preprocess = !InputArgs.hasArg(WINDRES_no_preprocess);422 Triple TT(Prefix);423 if (InputArgs.hasArg(WINDRES_target)) {424 StringRef Value = InputArgs.getLastArgValue(WINDRES_target);425 if (Value == "pe-i386")426 Opts.Triple = "i686-w64-mingw32";427 else if (Value == "pe-x86-64")428 Opts.Triple = "x86_64-w64-mingw32";429 else430 // Implicit extension; if the --target value isn't one of the known431 // BFD targets, allow setting the full triple string via this instead.432 Opts.Triple = Value.str();433 } else if (TT.getArch() != Triple::UnknownArch)434 Opts.Triple = Prefix;435 else436 Opts.Triple = getMingwTriple();437 438 for (const auto *Arg :439 InputArgs.filtered(WINDRES_include_dir, WINDRES_define, WINDRES_undef,440 WINDRES_preprocessor_arg)) {441 // GNU windres passes the arguments almost as-is on to popen() (it only442 // backslash escapes spaces in the arguments), where a shell would443 // unescape backslash escapes for quotes and similar. This means that444 // when calling GNU windres, callers need to double escape chars like445 // quotes, e.g. as -DSTRING=\\\"1.2.3\\\".446 //447 // Exactly how the arguments are interpreted depends on the platform448 // though - but the cases where this matters (where callers would have449 // done this double escaping) probably is confined to cases like these450 // quoted string defines, and those happen to work the same across unix451 // and windows.452 //453 // If GNU windres is executed with --use-temp-file, it doesn't use454 // popen() to invoke the preprocessor, but uses another function which455 // actually preserves tricky characters better. To mimic this behaviour,456 // don't unescape arguments here.457 std::string Value = Arg->getValue();458 if (!InputArgs.hasArg(WINDRES_use_temp_file))459 Value = unescape(Value);460 switch (Arg->getOption().getID()) {461 case WINDRES_include_dir:462 // Technically, these are handled the same way as e.g. defines, but463 // the way we consistently unescape the unix way breaks windows paths464 // with single backslashes. Alternatively, our unescape function would465 // need to mimic the platform specific command line parsing/unescaping466 // logic.467 Opts.Params.Include.push_back(Arg->getValue());468 Opts.PreprocessArgs.push_back("-I");469 Opts.PreprocessArgs.push_back(Arg->getValue());470 break;471 case WINDRES_define:472 Opts.PreprocessArgs.push_back("-D");473 Opts.PreprocessArgs.push_back(Value);474 break;475 case WINDRES_undef:476 Opts.PreprocessArgs.push_back("-U");477 Opts.PreprocessArgs.push_back(Value);478 break;479 case WINDRES_preprocessor_arg:480 Opts.PreprocessArgs.push_back(Value);481 break;482 }483 }484 if (InputArgs.hasArg(WINDRES_preprocessor))485 Opts.Preprocessor = InputArgs.getLastArgValue(WINDRES_preprocessor);486 487 Opts.Params.CodePage = CpWin1252; // Different default488 if (InputArgs.hasArg(WINDRES_codepage)) {489 if (InputArgs.getLastArgValue(WINDRES_codepage)490 .getAsInteger(0, Opts.Params.CodePage))491 fatalError("Invalid code page: " +492 InputArgs.getLastArgValue(WINDRES_codepage));493 }494 if (InputArgs.hasArg(WINDRES_language)) {495 StringRef Val = InputArgs.getLastArgValue(WINDRES_language);496 Val.consume_front_insensitive("0x");497 if (Val.getAsInteger(16, Opts.LangId))498 fatalError("Invalid language id: " +499 InputArgs.getLastArgValue(WINDRES_language));500 }501 502 Opts.BeVerbose = InputArgs.hasArg(WINDRES_verbose);503 504 return Opts;505}506 507RcOptions parseRcOptions(ArrayRef<const char *> ArgsArr,508 ArrayRef<const char *> InputArgsArray) {509 RcOptTable T;510 RcOptions Opts;511 unsigned MAI, MAC;512 opt::InputArgList InputArgs = T.ParseArgs(ArgsArr, MAI, MAC);513 514 // The tool prints nothing when invoked with no command-line arguments.515 if (InputArgs.hasArg(OPT_help)) {516 T.printHelp(outs(), "llvm-rc [options] file...", "LLVM Resource Converter",517 false);518 exit(0);519 }520 521 std::vector<std::string> InArgsInfo = InputArgs.getAllArgValues(OPT_INPUT);522 llvm::append_range(InArgsInfo, InputArgsArray);523 if (InArgsInfo.size() != 1) {524 fatalError("Exactly one input file should be provided.");525 }526 527 Opts.PrintCmdAndExit = InputArgs.hasArg(OPT__HASH_HASH_HASH);528 Opts.Triple = getClangClTriple();529 for (const auto *Arg :530 InputArgs.filtered(OPT_includepath, OPT_define, OPT_undef)) {531 switch (Arg->getOption().getID()) {532 case OPT_includepath:533 Opts.PreprocessArgs.push_back("-I");534 break;535 case OPT_define:536 Opts.PreprocessArgs.push_back("-D");537 break;538 case OPT_undef:539 Opts.PreprocessArgs.push_back("-U");540 break;541 }542 Opts.PreprocessArgs.push_back(Arg->getValue());543 }544 545 Opts.InputFile = InArgsInfo[0];546 Opts.BeVerbose = InputArgs.hasArg(OPT_verbose);547 Opts.Preprocess = !InputArgs.hasArg(OPT_no_preprocess);548 Opts.Params.Include = InputArgs.getAllArgValues(OPT_includepath);549 Opts.Params.NoInclude = InputArgs.hasArg(OPT_noinclude);550 if (Opts.Params.NoInclude) {551 // Clear the INLCUDE variable for the external preprocessor552#ifdef _WIN32553 ::_putenv("INCLUDE=");554#else555 ::unsetenv("INCLUDE");556#endif557 }558 if (InputArgs.hasArg(OPT_codepage)) {559 if (InputArgs.getLastArgValue(OPT_codepage)560 .getAsInteger(10, Opts.Params.CodePage))561 fatalError("Invalid code page: " +562 InputArgs.getLastArgValue(OPT_codepage));563 }564 Opts.IsDryRun = InputArgs.hasArg(OPT_dry_run);565 auto OutArgsInfo = InputArgs.getAllArgValues(OPT_fileout);566 if (OutArgsInfo.empty()) {567 SmallString<128> OutputFile(Opts.InputFile);568 llvm::sys::fs::make_absolute(OutputFile);569 llvm::sys::path::replace_extension(OutputFile, "res");570 OutArgsInfo.push_back(std::string(OutputFile));571 }572 if (!Opts.IsDryRun) {573 if (OutArgsInfo.size() != 1)574 fatalError(575 "No more than one output file should be provided (using /FO flag).");576 Opts.OutputFile = OutArgsInfo[0];577 }578 Opts.AppendNull = InputArgs.hasArg(OPT_add_null);579 if (InputArgs.hasArg(OPT_lang_id)) {580 StringRef Val = InputArgs.getLastArgValue(OPT_lang_id);581 Val.consume_front_insensitive("0x");582 if (Val.getAsInteger(16, Opts.LangId))583 fatalError("Invalid language id: " +584 InputArgs.getLastArgValue(OPT_lang_id));585 }586 return Opts;587}588 589RcOptions getOptions(const char *Argv0, ArrayRef<const char *> ArgsArr,590 ArrayRef<const char *> InputArgs) {591 std::string Prefix;592 bool IsWindres;593 std::tie(IsWindres, Prefix) = isWindres(Argv0);594 if (IsWindres)595 return parseWindresOptions(ArgsArr, InputArgs, Prefix);596 else597 return parseRcOptions(ArgsArr, InputArgs);598}599 600void doRc(std::string Src, std::string Dest, RcOptions &Opts,601 const char *Argv0) {602 std::string PreprocessedFile = Src;603 if (Opts.Preprocess) {604 std::string OutFile = createTempFile("preproc", "rc");605 TempPreprocFile.setFile(OutFile);606 preprocess(Src, OutFile, Opts, Argv0);607 PreprocessedFile = OutFile;608 }609 610 // Read and tokenize the input file.611 ErrorOr<std::unique_ptr<MemoryBuffer>> File =612 MemoryBuffer::getFile(PreprocessedFile, /*IsText=*/true);613 if (!File) {614 fatalError("Error opening file '" + Twine(PreprocessedFile) +615 "': " + File.getError().message());616 }617 618 std::unique_ptr<MemoryBuffer> FileContents = std::move(*File);619 StringRef Contents = FileContents->getBuffer();620 621 std::string FilteredContents = filterCppOutput(Contents);622 std::vector<RCToken> Tokens =623 ExitOnErr(tokenizeRC(FilteredContents, Opts.IsWindres));624 625 if (Opts.BeVerbose) {626 const Twine TokenNames[] = {627#define TOKEN(Name) #Name,628#define SHORT_TOKEN(Name, Ch) #Name,629#include "ResourceScriptTokenList.def"630 };631 632 for (const RCToken &Token : Tokens) {633 outs() << TokenNames[static_cast<int>(Token.kind())] << ": "634 << Token.value();635 if (Token.kind() == RCToken::Kind::Int)636 outs() << "; int value = " << Token.intValue();637 638 outs() << "\n";639 }640 }641 642 WriterParams &Params = Opts.Params;643 SmallString<128> InputFile(Src);644 llvm::sys::fs::make_absolute(InputFile);645 Params.InputFilePath = InputFile;646 647 switch (Params.CodePage) {648 case CpAcp:649 case CpWin1252:650 case CpUtf8:651 break;652 default:653 fatalError("Unsupported code page, only 0, 1252 and 65001 are supported!");654 }655 656 std::unique_ptr<ResourceFileWriter> Visitor;657 658 if (!Opts.IsDryRun) {659 std::error_code EC;660 auto FOut = std::make_unique<raw_fd_ostream>(661 Dest, EC, sys::fs::FA_Read | sys::fs::FA_Write);662 if (EC)663 fatalError("Error opening output file '" + Dest + "': " + EC.message());664 Visitor = std::make_unique<ResourceFileWriter>(Params, std::move(FOut));665 Visitor->AppendNull = Opts.AppendNull;666 667 ExitOnErr(NullResource().visit(Visitor.get()));668 669 unsigned PrimaryLangId = Opts.LangId & 0x3ff;670 unsigned SubLangId = Opts.LangId >> 10;671 ExitOnErr(LanguageResource(PrimaryLangId, SubLangId).visit(Visitor.get()));672 }673 674 rc::RCParser Parser{std::move(Tokens)};675 while (!Parser.isEof()) {676 auto Resource = ExitOnErr(Parser.parseSingleResource());677 if (Opts.BeVerbose)678 Resource->log(outs());679 if (!Opts.IsDryRun)680 ExitOnErr(Resource->visit(Visitor.get()));681 }682 683 // STRINGTABLE resources come at the very end.684 if (!Opts.IsDryRun)685 ExitOnErr(Visitor->dumpAllStringTables());686}687 688void doCvtres(std::string Src, std::string Dest, std::string TargetTriple) {689 object::WindowsResourceParser Parser;690 691 ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr =692 MemoryBuffer::getFile(Src, /*IsText=*/true);693 if (!BufferOrErr)694 fatalError("Error opening file '" + Twine(Src) +695 "': " + BufferOrErr.getError().message());696 std::unique_ptr<MemoryBuffer> &Buffer = BufferOrErr.get();697 std::unique_ptr<object::WindowsResource> Binary =698 ExitOnErr(object::WindowsResource::createWindowsResource(699 Buffer->getMemBufferRef()));700 701 std::vector<std::string> Duplicates;702 ExitOnErr(Parser.parse(Binary.get(), Duplicates));703 for (const auto &DupeDiag : Duplicates)704 fatalError("Duplicate resources: " + DupeDiag);705 706 Triple T(TargetTriple);707 COFF::MachineTypes MachineType;708 switch (T.getArch()) {709 case Triple::x86:710 MachineType = COFF::IMAGE_FILE_MACHINE_I386;711 break;712 case Triple::x86_64:713 MachineType = COFF::IMAGE_FILE_MACHINE_AMD64;714 break;715 case Triple::arm:716 case Triple::thumb:717 MachineType = COFF::IMAGE_FILE_MACHINE_ARMNT;718 break;719 case Triple::aarch64:720 if (T.isWindowsArm64EC())721 MachineType = COFF::IMAGE_FILE_MACHINE_ARM64EC;722 else723 MachineType = COFF::IMAGE_FILE_MACHINE_ARM64;724 break;725 default:726 fatalError("Unsupported architecture in target '" + Twine(TargetTriple) +727 "'");728 }729 730 std::unique_ptr<MemoryBuffer> OutputBuffer =731 ExitOnErr(object::writeWindowsResourceCOFF(MachineType, Parser,732 /*DateTimeStamp*/ 0));733 std::unique_ptr<FileOutputBuffer> FileBuffer =734 ExitOnErr(FileOutputBuffer::create(Dest, OutputBuffer->getBufferSize()));735 std::copy(OutputBuffer->getBufferStart(), OutputBuffer->getBufferEnd(),736 FileBuffer->getBufferStart());737 ExitOnErr(FileBuffer->commit());738}739 740} // anonymous namespace741 742int llvm_rc_main(int Argc, char **Argv, const llvm::ToolContext &) {743 ExitOnErr.setBanner("llvm-rc: ");744 745 char **DashDash = std::find_if(Argv + 1, Argv + Argc,746 [](StringRef Str) { return Str == "--"; });747 ArrayRef<const char *> ArgsArr = ArrayRef(Argv + 1, DashDash);748 ArrayRef<const char *> FileArgsArr;749 if (DashDash != Argv + Argc)750 FileArgsArr = ArrayRef(DashDash + 1, Argv + Argc);751 752 RcOptions Opts = getOptions(Argv[0], ArgsArr, FileArgsArr);753 754 std::string ResFile = Opts.OutputFile;755 if (Opts.InputFormat == Rc) {756 if (Opts.OutputFormat == Coff) {757 ResFile = createTempFile("rc", "res");758 TempResFile.setFile(ResFile);759 }760 doRc(Opts.InputFile, ResFile, Opts, Argv[0]);761 } else {762 ResFile = Opts.InputFile;763 }764 if (Opts.OutputFormat == Coff) {765 doCvtres(ResFile, Opts.OutputFile, Opts.Triple);766 }767 768 return 0;769}770