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