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