xref: /llvm-project/llvm/tools/llvm-rc/llvm-rc.cpp (revision 64bc44f5ddfb6da4b6a8b51ea9a03f8772b3ae95)
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/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/FileUtilities.h"
26 #include "llvm/Support/Host.h"
27 #include "llvm/Support/InitLLVM.h"
28 #include "llvm/Support/ManagedStatic.h"
29 #include "llvm/Support/MemoryBuffer.h"
30 #include "llvm/Support/Path.h"
31 #include "llvm/Support/PrettyStackTrace.h"
32 #include "llvm/Support/Process.h"
33 #include "llvm/Support/Program.h"
34 #include "llvm/Support/Signals.h"
35 #include "llvm/Support/StringSaver.h"
36 #include "llvm/Support/raw_ostream.h"
37 
38 #include <algorithm>
39 #include <system_error>
40 
41 using namespace llvm;
42 using namespace llvm::rc;
43 
44 namespace {
45 
46 // Input options tables.
47 
48 enum ID {
49   OPT_INVALID = 0, // This is not a correct option ID.
50 #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM,  \
51                HELPTEXT, METAVAR, VALUES)                                      \
52   OPT_##ID,
53 #include "Opts.inc"
54 #undef OPTION
55 };
56 
57 #define PREFIX(NAME, VALUE) const char *const NAME[] = VALUE;
58 #include "Opts.inc"
59 #undef PREFIX
60 
61 static const opt::OptTable::Info InfoTable[] = {
62 #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM,  \
63                HELPTEXT, METAVAR, VALUES)                                      \
64   {                                                                            \
65       PREFIX,      NAME,      HELPTEXT,                                        \
66       METAVAR,     OPT_##ID,  opt::Option::KIND##Class,                        \
67       PARAM,       FLAGS,     OPT_##GROUP,                                     \
68       OPT_##ALIAS, ALIASARGS, VALUES},
69 #include "Opts.inc"
70 #undef OPTION
71 };
72 
73 class RcOptTable : public opt::OptTable {
74 public:
75   RcOptTable() : OptTable(InfoTable, /* IgnoreCase = */ true) {}
76 };
77 
78 static ExitOnError ExitOnErr;
79 static FileRemover TempPreprocFile;
80 
81 LLVM_ATTRIBUTE_NORETURN static void fatalError(const Twine &Message) {
82   errs() << Message << "\n";
83   exit(1);
84 }
85 
86 std::string createTempFile(const Twine &Prefix, StringRef Suffix) {
87   std::error_code EC;
88   SmallString<128> FileName;
89   if ((EC = sys::fs::createTemporaryFile(Prefix, Suffix, FileName)))
90     fatalError("Unable to create temp file: " + EC.message());
91   return static_cast<std::string>(FileName);
92 }
93 
94 ErrorOr<std::string> findClang(const char *Argv0) {
95   StringRef Parent = llvm::sys::path::parent_path(Argv0);
96   ErrorOr<std::string> Path = std::error_code();
97   if (!Parent.empty()) {
98     // First look for the tool with all potential names in the specific
99     // directory of Argv0, if known
100     for (const auto *Name : {"clang", "clang-cl"}) {
101       Path = sys::findProgramByName(Name, Parent);
102       if (Path)
103         return Path;
104     }
105   }
106   // If no parent directory known, or not found there, look everywhere in PATH
107   for (const auto *Name : {"clang", "clang-cl"}) {
108     Path = sys::findProgramByName(Name);
109     if (Path)
110       return Path;
111   }
112   return Path;
113 }
114 
115 std::string getClangClTriple() {
116   Triple T(sys::getDefaultTargetTriple());
117   T.setOS(llvm::Triple::Win32);
118   T.setVendor(llvm::Triple::PC);
119   T.setEnvironment(llvm::Triple::MSVC);
120   T.setObjectFormat(llvm::Triple::COFF);
121   return T.str();
122 }
123 
124 bool preprocess(StringRef Src, StringRef Dst, opt::InputArgList &InputArgs,
125                 const char *Argv0) {
126   std::string Clang;
127   if (InputArgs.hasArg(OPT__HASH_HASH_HASH)) {
128     Clang = "clang";
129   } else {
130     ErrorOr<std::string> ClangOrErr = findClang(Argv0);
131     if (ClangOrErr) {
132       Clang = *ClangOrErr;
133     } else {
134       errs() << "llvm-rc: Unable to find clang, skipping preprocessing."
135              << "\n";
136       errs() << "Pass -no-cpp to disable preprocessing. This will be an error "
137                 "in the future."
138              << "\n";
139       return false;
140     }
141   }
142   std::string PreprocTriple = getClangClTriple();
143 
144   SmallVector<StringRef, 8> Args = {
145       Clang, "--driver-mode=gcc", "-target", PreprocTriple, "-E",
146       "-xc", "-DRC_INVOKED",      Src,       "-o",          Dst};
147   if (InputArgs.hasArg(OPT_noinclude)) {
148 #ifdef _WIN32
149     ::_putenv("INCLUDE=");
150 #else
151     ::unsetenv("INCLUDE");
152 #endif
153   }
154   for (const auto *Arg :
155        InputArgs.filtered(OPT_includepath, OPT_define, OPT_undef)) {
156     switch (Arg->getOption().getID()) {
157     case OPT_includepath:
158       Args.push_back("-I");
159       break;
160     case OPT_define:
161       Args.push_back("-D");
162       break;
163     case OPT_undef:
164       Args.push_back("-U");
165       break;
166     }
167     Args.push_back(Arg->getValue());
168   }
169   if (InputArgs.hasArg(OPT__HASH_HASH_HASH) || InputArgs.hasArg(OPT_verbose)) {
170     for (const auto &A : Args) {
171       outs() << " ";
172       sys::printArg(outs(), A, InputArgs.hasArg(OPT__HASH_HASH_HASH));
173     }
174     outs() << "\n";
175     if (InputArgs.hasArg(OPT__HASH_HASH_HASH))
176       exit(0);
177   }
178   // The llvm Support classes don't handle reading from stdout of a child
179   // process; otherwise we could avoid using a temp file.
180   int Res = sys::ExecuteAndWait(Clang, Args);
181   if (Res) {
182     fatalError("llvm-rc: Preprocessing failed.");
183   }
184   return true;
185 }
186 
187 } // anonymous namespace
188 
189 int main(int Argc, const char **Argv) {
190   InitLLVM X(Argc, Argv);
191   ExitOnErr.setBanner("llvm-rc: ");
192 
193   RcOptTable T;
194   unsigned MAI, MAC;
195   const char **DashDash = std::find_if(
196       Argv + 1, Argv + Argc, [](StringRef Str) { return Str == "--"; });
197   ArrayRef<const char *> ArgsArr = makeArrayRef(Argv + 1, DashDash);
198 
199   opt::InputArgList InputArgs = T.ParseArgs(ArgsArr, MAI, MAC);
200 
201   // The tool prints nothing when invoked with no command-line arguments.
202   if (InputArgs.hasArg(OPT_help)) {
203     T.PrintHelp(outs(), "rc [options] file...", "Resource Converter", false);
204     return 0;
205   }
206 
207   const bool BeVerbose = InputArgs.hasArg(OPT_verbose);
208 
209   std::vector<std::string> InArgsInfo = InputArgs.getAllArgValues(OPT_INPUT);
210   if (DashDash != Argv + Argc)
211     InArgsInfo.insert(InArgsInfo.end(), DashDash + 1, Argv + Argc);
212   if (InArgsInfo.size() != 1) {
213     fatalError("Exactly one input file should be provided.");
214   }
215 
216   std::string PreprocessedFile = InArgsInfo[0];
217   if (!InputArgs.hasArg(OPT_no_preprocess)) {
218     std::string OutFile = createTempFile("preproc", "rc");
219     TempPreprocFile.setFile(OutFile);
220     if (preprocess(InArgsInfo[0], OutFile, InputArgs, Argv[0]))
221       PreprocessedFile = OutFile;
222   }
223 
224   // Read and tokenize the input file.
225   ErrorOr<std::unique_ptr<MemoryBuffer>> File =
226       MemoryBuffer::getFile(PreprocessedFile);
227   if (!File) {
228     fatalError("Error opening file '" + Twine(InArgsInfo[0]) +
229                "': " + File.getError().message());
230   }
231 
232   std::unique_ptr<MemoryBuffer> FileContents = std::move(*File);
233   StringRef Contents = FileContents->getBuffer();
234 
235   std::string FilteredContents = filterCppOutput(Contents);
236   std::vector<RCToken> Tokens = ExitOnErr(tokenizeRC(FilteredContents));
237 
238   if (BeVerbose) {
239     const Twine TokenNames[] = {
240 #define TOKEN(Name) #Name,
241 #define SHORT_TOKEN(Name, Ch) #Name,
242 #include "ResourceScriptTokenList.def"
243     };
244 
245     for (const RCToken &Token : Tokens) {
246       outs() << TokenNames[static_cast<int>(Token.kind())] << ": "
247              << Token.value();
248       if (Token.kind() == RCToken::Kind::Int)
249         outs() << "; int value = " << Token.intValue();
250 
251       outs() << "\n";
252     }
253   }
254 
255   WriterParams Params;
256   SmallString<128> InputFile(InArgsInfo[0]);
257   llvm::sys::fs::make_absolute(InputFile);
258   Params.InputFilePath = InputFile;
259   Params.Include = InputArgs.getAllArgValues(OPT_includepath);
260   Params.NoInclude = InputArgs.hasArg(OPT_noinclude);
261 
262   if (InputArgs.hasArg(OPT_codepage)) {
263     if (InputArgs.getLastArgValue(OPT_codepage)
264             .getAsInteger(10, Params.CodePage))
265       fatalError("Invalid code page: " +
266                  InputArgs.getLastArgValue(OPT_codepage));
267     switch (Params.CodePage) {
268     case CpAcp:
269     case CpWin1252:
270     case CpUtf8:
271       break;
272     default:
273       fatalError(
274           "Unsupported code page, only 0, 1252 and 65001 are supported!");
275     }
276   }
277 
278   std::unique_ptr<ResourceFileWriter> Visitor;
279   bool IsDryRun = InputArgs.hasArg(OPT_dry_run);
280 
281   if (!IsDryRun) {
282     auto OutArgsInfo = InputArgs.getAllArgValues(OPT_fileout);
283     if (OutArgsInfo.empty()) {
284       SmallString<128> OutputFile = InputFile;
285       llvm::sys::path::replace_extension(OutputFile, "res");
286       OutArgsInfo.push_back(std::string(OutputFile.str()));
287     }
288 
289     if (OutArgsInfo.size() != 1)
290       fatalError(
291           "No more than one output file should be provided (using /FO flag).");
292 
293     std::error_code EC;
294     auto FOut = std::make_unique<raw_fd_ostream>(
295         OutArgsInfo[0], EC, sys::fs::FA_Read | sys::fs::FA_Write);
296     if (EC)
297       fatalError("Error opening output file '" + OutArgsInfo[0] +
298                  "': " + EC.message());
299     Visitor = std::make_unique<ResourceFileWriter>(Params, std::move(FOut));
300     Visitor->AppendNull = InputArgs.hasArg(OPT_add_null);
301 
302     ExitOnErr(NullResource().visit(Visitor.get()));
303 
304     // Set the default language; choose en-US arbitrarily.
305     unsigned PrimaryLangId = 0x09, SubLangId = 0x01;
306     if (InputArgs.hasArg(OPT_lang_id)) {
307       unsigned LangId;
308       if (InputArgs.getLastArgValue(OPT_lang_id).getAsInteger(16, LangId))
309         fatalError("Invalid language id: " +
310                    InputArgs.getLastArgValue(OPT_lang_id));
311       PrimaryLangId = LangId & 0x3ff;
312       SubLangId = LangId >> 10;
313     }
314     ExitOnErr(LanguageResource(PrimaryLangId, SubLangId).visit(Visitor.get()));
315   }
316 
317   rc::RCParser Parser{std::move(Tokens)};
318   while (!Parser.isEof()) {
319     auto Resource = ExitOnErr(Parser.parseSingleResource());
320     if (BeVerbose)
321       Resource->log(outs());
322     if (!IsDryRun)
323       ExitOnErr(Resource->visit(Visitor.get()));
324   }
325 
326   // STRINGTABLE resources come at the very end.
327   if (!IsDryRun)
328     ExitOnErr(Visitor->dumpAllStringTables());
329 
330   return 0;
331 }
332