xref: /llvm-project/llvm/tools/llvm-rc/llvm-rc.cpp (revision dd647e3e608ed0b2bac7c588d5859b80ef4a5976)
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