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