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