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
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 OPTION
57};
58
59namespace 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
68static 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
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 OPTION
88};
89
90namespace 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
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 OPTION
104};
105} // namespace windres_opt
106
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(status: 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, ResultPath&: FileName)))
129 fatalError(Message: "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: Argv0, MainExecAddr: 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(path: MainExecPath),
147 llvm::sys::path::parent_path(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, Paths: 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
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 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
185Triple::ArchType getDefaultFallbackArch() {
186 return Triple::x86_64;
187}
188
189std::string getClangClTriple() {
190 Triple T(sys::getDefaultTargetTriple());
191 if (!isUsableArch(Arch: T.getArch()))
192 T.setArch(Kind: 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(Arch: T.getArch()))
203 T.setArch(Kind: 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
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, Triple: 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(Message: "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(Elt: *Opts.Preprocessor);
262 if (!sys::fs::can_execute(Path: Args[0])) {
263 if (auto P = sys::findProgramByName(Name: Args[0])) {
264 PreprocessorExecutable = *P;
265 Args[0] = PreprocessorExecutable;
266 }
267 }
268 }
269 llvm::append_range(C&: Args, R: Opts.PreprocessArgs);
270 Args.push_back(Elt: Src);
271 Args.push_back(Elt: "-o");
272 Args.push_back(Elt: Dst);
273 if (Opts.PrintCmdAndExit || Opts.BeVerbose) {
274 for (const auto &A : Args) {
275 outs() << " ";
276 sys::printArg(OS&: outs(), Arg: A, Quote: Opts.PrintCmdAndExit);
277 }
278 outs() << "\n";
279 if (Opts.PrintCmdAndExit)
280 exit(status: 0);
281 }
282 // The llvm Support classes don't handle reading from stdout of a child
283 // process; otherwise we could avoid using a temp file.
284 std::string ErrMsg;
285 int Res =
286 sys::ExecuteAndWait(Program: Args[0], Args, /*Env=*/std::nullopt, /*Redirects=*/{},
287 /*SecondsToWait=*/0, /*MemoryLimit=*/0, ErrMsg: &ErrMsg);
288 if (Res) {
289 if (!ErrMsg.empty())
290 fatalError(Message: "llvm-rc: Preprocessing failed: " + ErrMsg);
291 else
292 fatalError(Message: "llvm-rc: Preprocessing failed.");
293 }
294}
295
296static std::pair<bool, std::string> isWindres(llvm::StringRef Argv0) {
297 StringRef ProgName = llvm::sys::path::stem(path: Argv0);
298 // x86_64-w64-mingw32-windres -> x86_64-w64-mingw32, windres
299 // llvm-rc -> "", llvm-rc
300 // aarch64-w64-mingw32-llvm-windres-10.exe -> aarch64-w64-mingw32, llvm-windres
301 ProgName = ProgName.rtrim(Chars: "0123456789.-");
302 if (!ProgName.consume_back_insensitive(Suffix: "windres"))
303 return std::make_pair<bool, std::string>(x: false, y: "");
304 ProgName.consume_back_insensitive(Suffix: "llvm-");
305 ProgName.consume_back_insensitive(Suffix: "-");
306 return std::make_pair<bool, std::string>(x: true, y: ProgName.str());
307}
308
309Format parseFormat(StringRef S) {
310 Format F = StringSwitch<Format>(S.lower())
311 .Case(S: "rc", Value: Rc)
312 .Case(S: "res", Value: Res)
313 .Case(S: "coff", Value: Coff)
314 .Default(Value: Unknown);
315 if (F == Unknown)
316 fatalError(Message: "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(path: File.lower()))
322 .Case(S: ".rc", Value: Rc)
323 .Case(S: ".res", Value: Res)
324 .Case(S: ".o", Value: Coff)
325 .Case(S: ".obj", Value: Coff)
326 .Default(Value: Unknown);
327 if (F != Unknown)
328 Dest = F;
329}
330
331std::string unescape(StringRef S) {
332 std::string Out;
333 Out.reserve(res_arg: 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(c: S[++I]);
338 else
339 fatalError(Message: "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(c: 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(Args: ArgsArr, MissingArgIndex&: MAI, MissingArgCount&: MAC);
357
358 Opts.IsWindres = true;
359
360 // The tool prints nothing when invoked with no command-line arguments.
361 if (InputArgs.hasArg(Ids: WINDRES_help)) {
362 T.printHelp(OS&: outs(), Usage: "windres [options] file...",
363 Title: "LLVM windres (GNU windres compatible)", ShowHidden: false, ShowAllAliases: true);
364 exit(status: 0);
365 }
366
367 if (InputArgs.hasArg(Ids: WINDRES_version)) {
368 outs() << "llvm-windres, compatible with GNU windres\n";
369 cl::PrintVersionMessage();
370 exit(status: 0);
371 }
372
373 std::vector<std::string> FileArgs = InputArgs.getAllArgValues(Id: WINDRES_INPUT);
374 llvm::append_range(C&: FileArgs, R&: InputArgsArray);
375
376 if (InputArgs.hasArg(Ids: WINDRES_input)) {
377 Opts.InputFile = InputArgs.getLastArgValue(Id: WINDRES_input).str();
378 } else if (!FileArgs.empty()) {
379 Opts.InputFile = FileArgs.front();
380 FileArgs.erase(position: FileArgs.begin());
381 } else {
382 // TODO: GNU windres takes input on stdin in this case.
383 fatalError(Message: "Missing input file");
384 }
385
386 if (InputArgs.hasArg(Ids: WINDRES_output)) {
387 Opts.OutputFile = InputArgs.getLastArgValue(Id: WINDRES_output).str();
388 } else if (!FileArgs.empty()) {
389 Opts.OutputFile = FileArgs.front();
390 FileArgs.erase(position: FileArgs.begin());
391 } else {
392 // TODO: GNU windres writes output in rc form to stdout in this case.
393 fatalError(Message: "Missing output file");
394 }
395
396 if (InputArgs.hasArg(Ids: WINDRES_input_format)) {
397 Opts.InputFormat =
398 parseFormat(S: InputArgs.getLastArgValue(Id: WINDRES_input_format));
399 } else {
400 deduceFormat(Dest&: Opts.InputFormat, File: Opts.InputFile);
401 }
402 if (Opts.InputFormat == Coff)
403 fatalError(Message: "Unsupported input format");
404
405 if (InputArgs.hasArg(Ids: WINDRES_output_format)) {
406 Opts.OutputFormat =
407 parseFormat(S: InputArgs.getLastArgValue(Id: WINDRES_output_format));
408 } else {
409 // The default in windres differs from the default in RcOptions
410 Opts.OutputFormat = Coff;
411 deduceFormat(Dest&: Opts.OutputFormat, File: Opts.OutputFile);
412 }
413 if (Opts.OutputFormat == Rc)
414 fatalError(Message: "Unsupported output format");
415 if (Opts.InputFormat == Opts.OutputFormat) {
416 outs() << "Nothing to do.\n";
417 exit(status: 0);
418 }
419
420 Opts.PrintCmdAndExit = InputArgs.hasArg(Ids: WINDRES__HASH_HASH_HASH);
421 Opts.Preprocess = !InputArgs.hasArg(Ids: WINDRES_no_preprocess);
422 Triple TT(Prefix);
423 if (InputArgs.hasArg(Ids: WINDRES_target)) {
424 StringRef Value = InputArgs.getLastArgValue(Id: 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 else
430 // Implicit extension; if the --target value isn't one of the known
431 // 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 else
436 Opts.Triple = getMingwTriple();
437
438 for (const auto *Arg :
439 InputArgs.filtered(Ids: WINDRES_include_dir, Ids: WINDRES_define, Ids: WINDRES_undef,
440 Ids: WINDRES_preprocessor_arg)) {
441 // GNU windres passes the arguments almost as-is on to popen() (it only
442 // backslash escapes spaces in the arguments), where a shell would
443 // unescape backslash escapes for quotes and similar. This means that
444 // when calling GNU windres, callers need to double escape chars like
445 // quotes, e.g. as -DSTRING=\\\"1.2.3\\\".
446 //
447 // Exactly how the arguments are interpreted depends on the platform
448 // though - but the cases where this matters (where callers would have
449 // done this double escaping) probably is confined to cases like these
450 // quoted string defines, and those happen to work the same across unix
451 // and windows.
452 //
453 // If GNU windres is executed with --use-temp-file, it doesn't use
454 // popen() to invoke the preprocessor, but uses another function which
455 // 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(Ids: WINDRES_use_temp_file))
459 Value = unescape(S: Value);
460 switch (Arg->getOption().getID()) {
461 case WINDRES_include_dir:
462 // Technically, these are handled the same way as e.g. defines, but
463 // the way we consistently unescape the unix way breaks windows paths
464 // with single backslashes. Alternatively, our unescape function would
465 // need to mimic the platform specific command line parsing/unescaping
466 // logic.
467 Opts.Params.Include.push_back(x: Arg->getValue());
468 Opts.PreprocessArgs.push_back(x: "-I");
469 Opts.PreprocessArgs.push_back(x: Arg->getValue());
470 break;
471 case WINDRES_define:
472 Opts.PreprocessArgs.push_back(x: "-D");
473 Opts.PreprocessArgs.push_back(x: Value);
474 break;
475 case WINDRES_undef:
476 Opts.PreprocessArgs.push_back(x: "-U");
477 Opts.PreprocessArgs.push_back(x: Value);
478 break;
479 case WINDRES_preprocessor_arg:
480 Opts.PreprocessArgs.push_back(x: Value);
481 break;
482 }
483 }
484 if (InputArgs.hasArg(Ids: WINDRES_preprocessor))
485 Opts.Preprocessor = InputArgs.getLastArgValue(Id: WINDRES_preprocessor);
486
487 Opts.Params.CodePage = CpWin1252; // Different default
488 if (InputArgs.hasArg(Ids: WINDRES_codepage)) {
489 if (InputArgs.getLastArgValue(Id: WINDRES_codepage)
490 .getAsInteger(Radix: 0, Result&: Opts.Params.CodePage))
491 fatalError(Message: "Invalid code page: " +
492 InputArgs.getLastArgValue(Id: WINDRES_codepage));
493 }
494 if (InputArgs.hasArg(Ids: WINDRES_language)) {
495 StringRef Val = InputArgs.getLastArgValue(Id: WINDRES_language);
496 Val.consume_front_insensitive(Prefix: "0x");
497 if (Val.getAsInteger(Radix: 16, Result&: Opts.LangId))
498 fatalError(Message: "Invalid language id: " +
499 InputArgs.getLastArgValue(Id: WINDRES_language));
500 }
501
502 Opts.BeVerbose = InputArgs.hasArg(Ids: 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(Args: ArgsArr, MissingArgIndex&: MAI, MissingArgCount&: MAC);
513
514 // The tool prints nothing when invoked with no command-line arguments.
515 if (InputArgs.hasArg(Ids: OPT_help)) {
516 T.printHelp(OS&: outs(), Usage: "llvm-rc [options] file...", Title: "LLVM Resource Converter",
517 ShowHidden: false);
518 exit(status: 0);
519 }
520
521 std::vector<std::string> InArgsInfo = InputArgs.getAllArgValues(Id: OPT_INPUT);
522 llvm::append_range(C&: InArgsInfo, R&: InputArgsArray);
523 if (InArgsInfo.size() != 1) {
524 fatalError(Message: "Exactly one input file should be provided.");
525 }
526
527 Opts.PrintCmdAndExit = InputArgs.hasArg(Ids: OPT__HASH_HASH_HASH);
528 Opts.Triple = getClangClTriple();
529 for (const auto *Arg :
530 InputArgs.filtered(Ids: OPT_includepath, Ids: OPT_define, Ids: OPT_undef)) {
531 switch (Arg->getOption().getID()) {
532 case OPT_includepath:
533 Opts.PreprocessArgs.push_back(x: "-I");
534 break;
535 case OPT_define:
536 Opts.PreprocessArgs.push_back(x: "-D");
537 break;
538 case OPT_undef:
539 Opts.PreprocessArgs.push_back(x: "-U");
540 break;
541 }
542 Opts.PreprocessArgs.push_back(x: Arg->getValue());
543 }
544
545 Opts.InputFile = InArgsInfo[0];
546 Opts.BeVerbose = InputArgs.hasArg(Ids: OPT_verbose);
547 Opts.Preprocess = !InputArgs.hasArg(Ids: OPT_no_preprocess);
548 Opts.Params.Include = InputArgs.getAllArgValues(Id: OPT_includepath);
549 Opts.Params.NoInclude = InputArgs.hasArg(Ids: OPT_noinclude);
550 if (Opts.Params.NoInclude) {
551 // Clear the INLCUDE variable for the external preprocessor
552#ifdef _WIN32
553 ::_putenv("INCLUDE=");
554#else
555 ::unsetenv(name: "INCLUDE");
556#endif
557 }
558 if (InputArgs.hasArg(Ids: OPT_codepage)) {
559 if (InputArgs.getLastArgValue(Id: OPT_codepage)
560 .getAsInteger(Radix: 10, Result&: Opts.Params.CodePage))
561 fatalError(Message: "Invalid code page: " +
562 InputArgs.getLastArgValue(Id: OPT_codepage));
563 }
564 Opts.IsDryRun = InputArgs.hasArg(Ids: OPT_dry_run);
565 auto OutArgsInfo = InputArgs.getAllArgValues(Id: OPT_fileout);
566 if (OutArgsInfo.empty()) {
567 SmallString<128> OutputFile(Opts.InputFile);
568 llvm::sys::fs::make_absolute(path&: OutputFile);
569 llvm::sys::path::replace_extension(path&: OutputFile, extension: "res");
570 OutArgsInfo.push_back(x: std::string(OutputFile));
571 }
572 if (!Opts.IsDryRun) {
573 if (OutArgsInfo.size() != 1)
574 fatalError(
575 Message: "No more than one output file should be provided (using /FO flag).");
576 Opts.OutputFile = OutArgsInfo[0];
577 }
578 Opts.AppendNull = InputArgs.hasArg(Ids: OPT_add_null);
579 if (InputArgs.hasArg(Ids: OPT_lang_id)) {
580 StringRef Val = InputArgs.getLastArgValue(Id: OPT_lang_id);
581 Val.consume_front_insensitive(Prefix: "0x");
582 if (Val.getAsInteger(Radix: 16, Result&: Opts.LangId))
583 fatalError(Message: "Invalid language id: " +
584 InputArgs.getLastArgValue(Id: 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(args&: IsWindres, args&: Prefix) = isWindres(Argv0);
594 if (IsWindres)
595 return parseWindresOptions(ArgsArr, InputArgsArray: InputArgs, Prefix);
596 else
597 return parseRcOptions(ArgsArr, InputArgsArray: 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(Prefix: "preproc", Suffix: "rc");
605 TempPreprocFile.setFile(filename: OutFile);
606 preprocess(Src, Dst: 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(Filename: PreprocessedFile, /*IsText=*/true);
613 if (!File) {
614 fatalError(Message: "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(Input: Contents);
622 std::vector<RCToken> Tokens = ExitOnErr(tokenizeRC(Input: FilteredContents));
623
624 if (Opts.BeVerbose) {
625 const Twine TokenNames[] = {
626#define TOKEN(Name) #Name,
627#define SHORT_TOKEN(Name, Ch) #Name,
628#include "ResourceScriptTokenList.def"
629 };
630
631 for (const RCToken &Token : Tokens) {
632 outs() << TokenNames[static_cast<int>(Token.kind())] << ": "
633 << Token.value();
634 if (Token.kind() == RCToken::Kind::Int)
635 outs() << "; int value = " << Token.intValue();
636
637 outs() << "\n";
638 }
639 }
640
641 WriterParams &Params = Opts.Params;
642 SmallString<128> InputFile(Src);
643 llvm::sys::fs::make_absolute(path&: InputFile);
644 Params.InputFilePath = InputFile;
645
646 switch (Params.CodePage) {
647 case CpAcp:
648 case CpWin1252:
649 case CpUtf8:
650 break;
651 default:
652 fatalError(Message: "Unsupported code page, only 0, 1252 and 65001 are supported!");
653 }
654
655 std::unique_ptr<ResourceFileWriter> Visitor;
656
657 if (!Opts.IsDryRun) {
658 std::error_code EC;
659 auto FOut = std::make_unique<raw_fd_ostream>(
660 args&: Dest, args&: EC, args: sys::fs::FA_Read | sys::fs::FA_Write);
661 if (EC)
662 fatalError(Message: "Error opening output file '" + Dest + "': " + EC.message());
663 Visitor = std::make_unique<ResourceFileWriter>(args&: Params, args: std::move(FOut));
664 Visitor->AppendNull = Opts.AppendNull;
665
666 ExitOnErr(NullResource().visit(V: Visitor.get()));
667
668 unsigned PrimaryLangId = Opts.LangId & 0x3ff;
669 unsigned SubLangId = Opts.LangId >> 10;
670 ExitOnErr(LanguageResource(PrimaryLangId, SubLangId).visit(V: Visitor.get()));
671 }
672
673 rc::RCParser Parser{std::move(Tokens)};
674 while (!Parser.isEof()) {
675 auto Resource = ExitOnErr(Parser.parseSingleResource());
676 if (Opts.BeVerbose)
677 Resource->log(OS&: outs());
678 if (!Opts.IsDryRun)
679 ExitOnErr(Resource->visit(Visitor.get()));
680 }
681
682 // STRINGTABLE resources come at the very end.
683 if (!Opts.IsDryRun)
684 ExitOnErr(Visitor->dumpAllStringTables());
685}
686
687void doCvtres(std::string Src, std::string Dest, std::string TargetTriple) {
688 object::WindowsResourceParser Parser;
689
690 ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr =
691 MemoryBuffer::getFile(Filename: Src, /*IsText=*/true);
692 if (!BufferOrErr)
693 fatalError(Message: "Error opening file '" + Twine(Src) +
694 "': " + BufferOrErr.getError().message());
695 std::unique_ptr<MemoryBuffer> &Buffer = BufferOrErr.get();
696 std::unique_ptr<object::WindowsResource> Binary =
697 ExitOnErr(object::WindowsResource::createWindowsResource(
698 Source: Buffer->getMemBufferRef()));
699
700 std::vector<std::string> Duplicates;
701 ExitOnErr(Parser.parse(WR: Binary.get(), Duplicates));
702 for (const auto &DupeDiag : Duplicates)
703 fatalError(Message: "Duplicate resources: " + DupeDiag);
704
705 Triple T(TargetTriple);
706 COFF::MachineTypes MachineType;
707 switch (T.getArch()) {
708 case Triple::x86:
709 MachineType = COFF::IMAGE_FILE_MACHINE_I386;
710 break;
711 case Triple::x86_64:
712 MachineType = COFF::IMAGE_FILE_MACHINE_AMD64;
713 break;
714 case Triple::arm:
715 case Triple::thumb:
716 MachineType = COFF::IMAGE_FILE_MACHINE_ARMNT;
717 break;
718 case Triple::aarch64:
719 if (T.isWindowsArm64EC())
720 MachineType = COFF::IMAGE_FILE_MACHINE_ARM64EC;
721 else
722 MachineType = COFF::IMAGE_FILE_MACHINE_ARM64;
723 break;
724 default:
725 fatalError(Message: "Unsupported architecture in target '" + Twine(TargetTriple) +
726 "'");
727 }
728
729 std::unique_ptr<MemoryBuffer> OutputBuffer =
730 ExitOnErr(object::writeWindowsResourceCOFF(MachineType, Parser,
731 /*DateTimeStamp*/ TimeDateStamp: 0));
732 std::unique_ptr<FileOutputBuffer> FileBuffer =
733 ExitOnErr(FileOutputBuffer::create(FilePath: Dest, Size: OutputBuffer->getBufferSize()));
734 std::copy(first: OutputBuffer->getBufferStart(), last: OutputBuffer->getBufferEnd(),
735 result: FileBuffer->getBufferStart());
736 ExitOnErr(FileBuffer->commit());
737}
738
739} // anonymous namespace
740
741int llvm_rc_main(int Argc, char **Argv, const llvm::ToolContext &) {
742 ExitOnErr.setBanner("llvm-rc: ");
743
744 char **DashDash = std::find_if(first: Argv + 1, last: Argv + Argc,
745 pred: [](StringRef Str) { return Str == "--"; });
746 ArrayRef<const char *> ArgsArr = ArrayRef(Argv + 1, DashDash);
747 ArrayRef<const char *> FileArgsArr;
748 if (DashDash != Argv + Argc)
749 FileArgsArr = ArrayRef(DashDash + 1, Argv + Argc);
750
751 RcOptions Opts = getOptions(Argv0: Argv[0], ArgsArr, InputArgs: FileArgsArr);
752
753 std::string ResFile = Opts.OutputFile;
754 if (Opts.InputFormat == Rc) {
755 if (Opts.OutputFormat == Coff) {
756 ResFile = createTempFile(Prefix: "rc", Suffix: "res");
757 TempResFile.setFile(filename: ResFile);
758 }
759 doRc(Src: Opts.InputFile, Dest: ResFile, Opts, Argv0: Argv[0]);
760 } else {
761 ResFile = Opts.InputFile;
762 }
763 if (Opts.OutputFormat == Coff) {
764 doCvtres(Src: ResFile, Dest: Opts.OutputFile, TargetTriple: Opts.Triple);
765 }
766
767 return 0;
768}
769