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
getDefaultArch(Triple::ArchType Arch)148 Triple::ArchType getDefaultArch(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 Arch;
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 set a reasonable default arch.
161 return Triple::x86_64;
162 }
163 }
164
getClangClTriple()165 std::string getClangClTriple() {
166 Triple T(sys::getDefaultTargetTriple());
167 T.setArch(getDefaultArch(T.getArch()));
168 T.setOS(Triple::Win32);
169 T.setVendor(Triple::PC);
170 T.setEnvironment(Triple::MSVC);
171 T.setObjectFormat(Triple::COFF);
172 return T.str();
173 }
174
getMingwTriple()175 std::string getMingwTriple() {
176 Triple T(sys::getDefaultTargetTriple());
177 T.setArch(getDefaultArch(T.getArch()));
178 if (T.isWindowsGNUEnvironment())
179 return T.str();
180 // Write out the literal form of the vendor/env here, instead of
181 // constructing them with enum values (which end up with them in
182 // normalized form). The literal form of the triple can matter for
183 // finding include files.
184 return (Twine(T.getArchName()) + "-w64-mingw32").str();
185 }
186
187 enum Format { Rc, Res, Coff, Unknown };
188
189 struct RcOptions {
190 bool Preprocess = true;
191 bool PrintCmdAndExit = false;
192 std::string Triple;
193 std::vector<std::string> PreprocessCmd;
194 std::vector<std::string> PreprocessArgs;
195
196 std::string InputFile;
197 Format InputFormat = Rc;
198 std::string OutputFile;
199 Format OutputFormat = Res;
200
201 bool BeVerbose = false;
202 WriterParams Params;
203 bool AppendNull = false;
204 bool IsDryRun = false;
205 // Set the default language; choose en-US arbitrarily.
206 unsigned LangId = (/*PrimaryLangId*/ 0x09) | (/*SubLangId*/ 0x01 << 10);
207 };
208
preprocess(StringRef Src,StringRef Dst,const RcOptions & Opts,const char * Argv0)209 bool preprocess(StringRef Src, StringRef Dst, const RcOptions &Opts,
210 const char *Argv0) {
211 std::string Clang;
212 if (Opts.PrintCmdAndExit) {
213 Clang = "clang";
214 } else {
215 ErrorOr<std::string> ClangOrErr = findClang(Argv0);
216 if (ClangOrErr) {
217 Clang = *ClangOrErr;
218 } else {
219 errs() << "llvm-rc: Unable to find clang, skipping preprocessing."
220 << "\n";
221 errs() << "Pass -no-cpp to disable preprocessing. This will be an error "
222 "in the future."
223 << "\n";
224 return false;
225 }
226 }
227
228 SmallVector<StringRef, 8> Args = {
229 Clang, "--driver-mode=gcc", "-target", Opts.Triple, "-E",
230 "-xc", "-DRC_INVOKED"};
231 if (!Opts.PreprocessCmd.empty()) {
232 Args.clear();
233 for (const auto &S : Opts.PreprocessCmd)
234 Args.push_back(S);
235 }
236 Args.push_back(Src);
237 Args.push_back("-o");
238 Args.push_back(Dst);
239 for (const auto &S : Opts.PreprocessArgs)
240 Args.push_back(S);
241 if (Opts.PrintCmdAndExit || Opts.BeVerbose) {
242 for (const auto &A : Args) {
243 outs() << " ";
244 sys::printArg(outs(), A, Opts.PrintCmdAndExit);
245 }
246 outs() << "\n";
247 if (Opts.PrintCmdAndExit)
248 exit(0);
249 }
250 // The llvm Support classes don't handle reading from stdout of a child
251 // process; otherwise we could avoid using a temp file.
252 int Res = sys::ExecuteAndWait(Clang, Args);
253 if (Res) {
254 fatalError("llvm-rc: Preprocessing failed.");
255 }
256 return true;
257 }
258
consume_back_lower(StringRef & S,const char * Str)259 static bool consume_back_lower(StringRef &S, const char *Str) {
260 if (!S.endswith_lower(Str))
261 return false;
262 S = S.drop_back(strlen(Str));
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 (!consume_back_lower(ProgName, "windres"))
273 return std::make_pair<bool, std::string>(false, "");
274 consume_back_lower(ProgName, "llvm-");
275 consume_back_lower(ProgName, "-");
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(10, Opts.Params.CodePage))
480 fatalError("Invalid code page: " +
481 InputArgs.getLastArgValue(WINDRES_codepage));
482 }
483 if (InputArgs.hasArg(WINDRES_language)) {
484 if (InputArgs.getLastArgValue(WINDRES_language)
485 .getAsInteger(16, Opts.LangId))
486 fatalError("Invalid language id: " +
487 InputArgs.getLastArgValue(WINDRES_language));
488 }
489
490 Opts.BeVerbose = InputArgs.hasArg(WINDRES_verbose);
491
492 return Opts;
493 }
494
parseRcOptions(ArrayRef<const char * > ArgsArr,ArrayRef<const char * > InputArgsArray)495 RcOptions parseRcOptions(ArrayRef<const char *> ArgsArr,
496 ArrayRef<const char *> InputArgsArray) {
497 RcOptTable T;
498 RcOptions Opts;
499 unsigned MAI, MAC;
500 opt::InputArgList InputArgs = T.ParseArgs(ArgsArr, MAI, MAC);
501
502 // The tool prints nothing when invoked with no command-line arguments.
503 if (InputArgs.hasArg(OPT_help)) {
504 T.PrintHelp(outs(), "rc [options] file...", "Resource Converter", false);
505 exit(0);
506 }
507
508 std::vector<std::string> InArgsInfo = InputArgs.getAllArgValues(OPT_INPUT);
509 InArgsInfo.insert(InArgsInfo.end(), InputArgsArray.begin(),
510 InputArgsArray.end());
511 if (InArgsInfo.size() != 1) {
512 fatalError("Exactly one input file should be provided.");
513 }
514
515 Opts.PrintCmdAndExit = InputArgs.hasArg(OPT__HASH_HASH_HASH);
516 Opts.Triple = getClangClTriple();
517 for (const auto *Arg :
518 InputArgs.filtered(OPT_includepath, OPT_define, OPT_undef)) {
519 switch (Arg->getOption().getID()) {
520 case OPT_includepath:
521 Opts.PreprocessArgs.push_back("-I");
522 break;
523 case OPT_define:
524 Opts.PreprocessArgs.push_back("-D");
525 break;
526 case OPT_undef:
527 Opts.PreprocessArgs.push_back("-U");
528 break;
529 }
530 Opts.PreprocessArgs.push_back(Arg->getValue());
531 }
532
533 Opts.InputFile = InArgsInfo[0];
534 Opts.BeVerbose = InputArgs.hasArg(OPT_verbose);
535 Opts.Preprocess = !InputArgs.hasArg(OPT_no_preprocess);
536 Opts.Params.Include = InputArgs.getAllArgValues(OPT_includepath);
537 Opts.Params.NoInclude = InputArgs.hasArg(OPT_noinclude);
538 if (Opts.Params.NoInclude) {
539 // Clear the INLCUDE variable for the external preprocessor
540 #ifdef _WIN32
541 ::_putenv("INCLUDE=");
542 #else
543 ::unsetenv("INCLUDE");
544 #endif
545 }
546 if (InputArgs.hasArg(OPT_codepage)) {
547 if (InputArgs.getLastArgValue(OPT_codepage)
548 .getAsInteger(10, Opts.Params.CodePage))
549 fatalError("Invalid code page: " +
550 InputArgs.getLastArgValue(OPT_codepage));
551 }
552 Opts.IsDryRun = InputArgs.hasArg(OPT_dry_run);
553 auto OutArgsInfo = InputArgs.getAllArgValues(OPT_fileout);
554 if (OutArgsInfo.empty()) {
555 SmallString<128> OutputFile(Opts.InputFile);
556 llvm::sys::fs::make_absolute(OutputFile);
557 llvm::sys::path::replace_extension(OutputFile, "res");
558 OutArgsInfo.push_back(std::string(OutputFile.str()));
559 }
560 if (!Opts.IsDryRun) {
561 if (OutArgsInfo.size() != 1)
562 fatalError(
563 "No more than one output file should be provided (using /FO flag).");
564 Opts.OutputFile = OutArgsInfo[0];
565 }
566 Opts.AppendNull = InputArgs.hasArg(OPT_add_null);
567 if (InputArgs.hasArg(OPT_lang_id)) {
568 if (InputArgs.getLastArgValue(OPT_lang_id).getAsInteger(16, Opts.LangId))
569 fatalError("Invalid language id: " +
570 InputArgs.getLastArgValue(OPT_lang_id));
571 }
572 return Opts;
573 }
574
getOptions(const char * Argv0,ArrayRef<const char * > ArgsArr,ArrayRef<const char * > InputArgs)575 RcOptions getOptions(const char *Argv0, ArrayRef<const char *> ArgsArr,
576 ArrayRef<const char *> InputArgs) {
577 std::string Prefix;
578 bool IsWindres;
579 std::tie(IsWindres, Prefix) = isWindres(Argv0);
580 if (IsWindres)
581 return parseWindresOptions(ArgsArr, InputArgs, Prefix);
582 else
583 return parseRcOptions(ArgsArr, InputArgs);
584 }
585
doRc(std::string Src,std::string Dest,RcOptions & Opts,const char * Argv0)586 void doRc(std::string Src, std::string Dest, RcOptions &Opts,
587 const char *Argv0) {
588 std::string PreprocessedFile = Src;
589 if (Opts.Preprocess) {
590 std::string OutFile = createTempFile("preproc", "rc");
591 TempPreprocFile.setFile(OutFile);
592 if (preprocess(Src, OutFile, Opts, Argv0))
593 PreprocessedFile = OutFile;
594 }
595
596 // Read and tokenize the input file.
597 ErrorOr<std::unique_ptr<MemoryBuffer>> File =
598 MemoryBuffer::getFile(PreprocessedFile);
599 if (!File) {
600 fatalError("Error opening file '" + Twine(PreprocessedFile) +
601 "': " + File.getError().message());
602 }
603
604 std::unique_ptr<MemoryBuffer> FileContents = std::move(*File);
605 StringRef Contents = FileContents->getBuffer();
606
607 std::string FilteredContents = filterCppOutput(Contents);
608 std::vector<RCToken> Tokens = ExitOnErr(tokenizeRC(FilteredContents));
609
610 if (Opts.BeVerbose) {
611 const Twine TokenNames[] = {
612 #define TOKEN(Name) #Name,
613 #define SHORT_TOKEN(Name, Ch) #Name,
614 #include "ResourceScriptTokenList.def"
615 };
616
617 for (const RCToken &Token : Tokens) {
618 outs() << TokenNames[static_cast<int>(Token.kind())] << ": "
619 << Token.value();
620 if (Token.kind() == RCToken::Kind::Int)
621 outs() << "; int value = " << Token.intValue();
622
623 outs() << "\n";
624 }
625 }
626
627 WriterParams &Params = Opts.Params;
628 SmallString<128> InputFile(Src);
629 llvm::sys::fs::make_absolute(InputFile);
630 Params.InputFilePath = InputFile;
631
632 switch (Params.CodePage) {
633 case CpAcp:
634 case CpWin1252:
635 case CpUtf8:
636 break;
637 default:
638 fatalError("Unsupported code page, only 0, 1252 and 65001 are supported!");
639 }
640
641 std::unique_ptr<ResourceFileWriter> Visitor;
642
643 if (!Opts.IsDryRun) {
644 std::error_code EC;
645 auto FOut = std::make_unique<raw_fd_ostream>(
646 Dest, EC, sys::fs::FA_Read | sys::fs::FA_Write);
647 if (EC)
648 fatalError("Error opening output file '" + Dest + "': " + EC.message());
649 Visitor = std::make_unique<ResourceFileWriter>(Params, std::move(FOut));
650 Visitor->AppendNull = Opts.AppendNull;
651
652 ExitOnErr(NullResource().visit(Visitor.get()));
653
654 unsigned PrimaryLangId = Opts.LangId & 0x3ff;
655 unsigned SubLangId = Opts.LangId >> 10;
656 ExitOnErr(LanguageResource(PrimaryLangId, SubLangId).visit(Visitor.get()));
657 }
658
659 rc::RCParser Parser{std::move(Tokens)};
660 while (!Parser.isEof()) {
661 auto Resource = ExitOnErr(Parser.parseSingleResource());
662 if (Opts.BeVerbose)
663 Resource->log(outs());
664 if (!Opts.IsDryRun)
665 ExitOnErr(Resource->visit(Visitor.get()));
666 }
667
668 // STRINGTABLE resources come at the very end.
669 if (!Opts.IsDryRun)
670 ExitOnErr(Visitor->dumpAllStringTables());
671 }
672
doCvtres(std::string Src,std::string Dest,std::string TargetTriple)673 void doCvtres(std::string Src, std::string Dest, std::string TargetTriple) {
674 object::WindowsResourceParser Parser;
675
676 ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr =
677 MemoryBuffer::getFile(Src);
678 if (!BufferOrErr)
679 fatalError("Error opening file '" + Twine(Src) +
680 "': " + BufferOrErr.getError().message());
681 std::unique_ptr<MemoryBuffer> &Buffer = BufferOrErr.get();
682 std::unique_ptr<object::WindowsResource> Binary =
683 ExitOnErr(object::WindowsResource::createWindowsResource(
684 Buffer->getMemBufferRef()));
685
686 std::vector<std::string> Duplicates;
687 ExitOnErr(Parser.parse(Binary.get(), Duplicates));
688 for (const auto &DupeDiag : Duplicates)
689 fatalError("Duplicate resources: " + DupeDiag);
690
691 Triple T(TargetTriple);
692 COFF::MachineTypes MachineType;
693 switch (T.getArch()) {
694 case Triple::x86:
695 MachineType = COFF::IMAGE_FILE_MACHINE_I386;
696 break;
697 case Triple::x86_64:
698 MachineType = COFF::IMAGE_FILE_MACHINE_AMD64;
699 break;
700 case Triple::arm:
701 case Triple::thumb:
702 MachineType = COFF::IMAGE_FILE_MACHINE_ARMNT;
703 break;
704 case Triple::aarch64:
705 MachineType = COFF::IMAGE_FILE_MACHINE_ARM64;
706 break;
707 default:
708 fatalError("Unsupported architecture in target '" + Twine(TargetTriple) +
709 "'");
710 }
711
712 std::unique_ptr<MemoryBuffer> OutputBuffer =
713 ExitOnErr(object::writeWindowsResourceCOFF(MachineType, Parser,
714 /*DateTimeStamp*/ 0));
715 std::unique_ptr<FileOutputBuffer> FileBuffer =
716 ExitOnErr(FileOutputBuffer::create(Dest, OutputBuffer->getBufferSize()));
717 std::copy(OutputBuffer->getBufferStart(), OutputBuffer->getBufferEnd(),
718 FileBuffer->getBufferStart());
719 ExitOnErr(FileBuffer->commit());
720 }
721
722 } // anonymous namespace
723
main(int Argc,const char ** Argv)724 int main(int Argc, const char **Argv) {
725 InitLLVM X(Argc, Argv);
726 ExitOnErr.setBanner("llvm-rc: ");
727
728 const char **DashDash = std::find_if(
729 Argv + 1, Argv + Argc, [](StringRef Str) { return Str == "--"; });
730 ArrayRef<const char *> ArgsArr = makeArrayRef(Argv + 1, DashDash);
731 ArrayRef<const char *> FileArgsArr;
732 if (DashDash != Argv + Argc)
733 FileArgsArr = makeArrayRef(DashDash + 1, Argv + Argc);
734
735 RcOptions Opts = getOptions(Argv[0], ArgsArr, FileArgsArr);
736
737 std::string ResFile = Opts.OutputFile;
738 if (Opts.InputFormat == Rc) {
739 if (Opts.OutputFormat == Coff) {
740 ResFile = createTempFile("rc", "res");
741 TempResFile.setFile(ResFile);
742 }
743 doRc(Opts.InputFile, ResFile, Opts, Argv[0]);
744 } else {
745 ResFile = Opts.InputFile;
746 }
747 if (Opts.OutputFormat == Coff) {
748 doCvtres(ResFile, Opts.OutputFile, Opts.Triple);
749 }
750
751 return 0;
752 }
753