1 //===-- llvm-rc.cpp - Compile .rc scripts into .res -------------*- C++ -*-===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 //
10 // Compile .rc scripts into .res files. This is intended to be a
11 // platform-independent port of Microsoft's rc.exe tool.
12 //
13 //===----------------------------------------------------------------------===//
14 
15 #include "ResourceFileWriter.h"
16 #include "ResourceScriptCppFilter.h"
17 #include "ResourceScriptParser.h"
18 #include "ResourceScriptStmt.h"
19 #include "ResourceScriptToken.h"
20 
21 #include "llvm/Option/Arg.h"
22 #include "llvm/Option/ArgList.h"
23 #include "llvm/Support/Error.h"
24 #include "llvm/Support/FileSystem.h"
25 #include "llvm/Support/InitLLVM.h"
26 #include "llvm/Support/ManagedStatic.h"
27 #include "llvm/Support/MemoryBuffer.h"
28 #include "llvm/Support/Path.h"
29 #include "llvm/Support/PrettyStackTrace.h"
30 #include "llvm/Support/Process.h"
31 #include "llvm/Support/Signals.h"
32 #include "llvm/Support/raw_ostream.h"
33 
34 #include <system_error>
35 
36 using namespace llvm;
37 using namespace llvm::rc;
38 
39 namespace {
40 
41 // Input options tables.
42 
43 enum ID {
44   OPT_INVALID = 0, // This is not a correct option ID.
45 #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM,  \
46                HELPTEXT, METAVAR, VALUES)                                      \
47   OPT_##ID,
48 #include "Opts.inc"
49 #undef OPTION
50 };
51 
52 #define PREFIX(NAME, VALUE) const char *const NAME[] = VALUE;
53 #include "Opts.inc"
54 #undef PREFIX
55 
56 static const opt::OptTable::Info InfoTable[] = {
57 #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM,  \
58                HELPTEXT, METAVAR, VALUES)                                      \
59   {                                                                            \
60       PREFIX,      NAME,      HELPTEXT,                                        \
61       METAVAR,     OPT_##ID,  opt::Option::KIND##Class,                        \
62       PARAM,       FLAGS,     OPT_##GROUP,                                     \
63       OPT_##ALIAS, ALIASARGS, VALUES},
64 #include "Opts.inc"
65 #undef OPTION
66 };
67 
68 class RcOptTable : public opt::OptTable {
69 public:
RcOptTable()70   RcOptTable() : OptTable(InfoTable, /* IgnoreCase = */ true) {}
71 };
72 
73 static ExitOnError ExitOnErr;
74 
fatalError(const Twine & Message)75 LLVM_ATTRIBUTE_NORETURN static void fatalError(const Twine &Message) {
76   errs() << Message << "\n";
77   exit(1);
78 }
79 
80 } // anonymous namespace
81 
main(int Argc,const char ** Argv)82 int main(int Argc, const char **Argv) {
83   InitLLVM X(Argc, Argv);
84   ExitOnErr.setBanner("llvm-rc: ");
85 
86   RcOptTable T;
87   unsigned MAI, MAC;
88   ArrayRef<const char *> ArgsArr = makeArrayRef(Argv + 1, Argc - 1);
89   opt::InputArgList InputArgs = T.ParseArgs(ArgsArr, MAI, MAC);
90 
91   // The tool prints nothing when invoked with no command-line arguments.
92   if (InputArgs.hasArg(OPT_HELP)) {
93     T.PrintHelp(outs(), "rc", "Resource Converter", false);
94     return 0;
95   }
96 
97   const bool BeVerbose = InputArgs.hasArg(OPT_VERBOSE);
98 
99   std::vector<std::string> InArgsInfo = InputArgs.getAllArgValues(OPT_INPUT);
100   if (InArgsInfo.size() != 1) {
101     fatalError("Exactly one input file should be provided.");
102   }
103 
104   // Read and tokenize the input file.
105   ErrorOr<std::unique_ptr<MemoryBuffer>> File =
106       MemoryBuffer::getFile(InArgsInfo[0]);
107   if (!File) {
108     fatalError("Error opening file '" + Twine(InArgsInfo[0]) +
109                "': " + File.getError().message());
110   }
111 
112   std::unique_ptr<MemoryBuffer> FileContents = std::move(*File);
113   StringRef Contents = FileContents->getBuffer();
114 
115   std::string FilteredContents = filterCppOutput(Contents);
116   std::vector<RCToken> Tokens = ExitOnErr(tokenizeRC(FilteredContents));
117 
118   if (BeVerbose) {
119     const Twine TokenNames[] = {
120 #define TOKEN(Name) #Name,
121 #define SHORT_TOKEN(Name, Ch) #Name,
122 #include "ResourceScriptTokenList.def"
123     };
124 
125     for (const RCToken &Token : Tokens) {
126       outs() << TokenNames[static_cast<int>(Token.kind())] << ": "
127              << Token.value();
128       if (Token.kind() == RCToken::Kind::Int)
129         outs() << "; int value = " << Token.intValue();
130 
131       outs() << "\n";
132     }
133   }
134 
135   WriterParams Params;
136   SmallString<128> InputFile(InArgsInfo[0]);
137   llvm::sys::fs::make_absolute(InputFile);
138   Params.InputFilePath = InputFile;
139   Params.Include = InputArgs.getAllArgValues(OPT_INCLUDE);
140   Params.NoInclude = InputArgs.getAllArgValues(OPT_NOINCLUDE);
141 
142   if (InputArgs.hasArg(OPT_CODEPAGE)) {
143     if (InputArgs.getLastArgValue(OPT_CODEPAGE)
144             .getAsInteger(10, Params.CodePage))
145       fatalError("Invalid code page: " +
146                  InputArgs.getLastArgValue(OPT_CODEPAGE));
147     switch (Params.CodePage) {
148     case CpAcp:
149     case CpWin1252:
150     case CpUtf8:
151       break;
152     default:
153       fatalError(
154           "Unsupported code page, only 0, 1252 and 65001 are supported!");
155     }
156   }
157 
158   std::unique_ptr<ResourceFileWriter> Visitor;
159   bool IsDryRun = InputArgs.hasArg(OPT_DRY_RUN);
160 
161   if (!IsDryRun) {
162     auto OutArgsInfo = InputArgs.getAllArgValues(OPT_FILEOUT);
163     if (OutArgsInfo.empty()) {
164       SmallString<128> OutputFile = InputFile;
165       llvm::sys::path::replace_extension(OutputFile, "res");
166       OutArgsInfo.push_back(OutputFile.str());
167     }
168 
169     if (OutArgsInfo.size() != 1)
170       fatalError(
171           "No more than one output file should be provided (using /FO flag).");
172 
173     std::error_code EC;
174     auto FOut = llvm::make_unique<raw_fd_ostream>(
175         OutArgsInfo[0], EC, sys::fs::FA_Read | sys::fs::FA_Write);
176     if (EC)
177       fatalError("Error opening output file '" + OutArgsInfo[0] +
178                  "': " + EC.message());
179     Visitor = llvm::make_unique<ResourceFileWriter>(Params, std::move(FOut));
180     Visitor->AppendNull = InputArgs.hasArg(OPT_ADD_NULL);
181 
182     ExitOnErr(NullResource().visit(Visitor.get()));
183 
184     // Set the default language; choose en-US arbitrarily.
185     ExitOnErr(LanguageResource(0x09, 0x01).visit(Visitor.get()));
186   }
187 
188   rc::RCParser Parser{std::move(Tokens)};
189   while (!Parser.isEof()) {
190     auto Resource = ExitOnErr(Parser.parseSingleResource());
191     if (BeVerbose)
192       Resource->log(outs());
193     if (!IsDryRun)
194       ExitOnErr(Resource->visit(Visitor.get()));
195   }
196 
197   // STRINGTABLE resources come at the very end.
198   if (!IsDryRun)
199     ExitOnErr(Visitor->dumpAllStringTables());
200 
201   return 0;
202 }
203