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   switch (T.getArch()) {
118   case Triple::x86:
119   case Triple::x86_64:
120   case Triple::arm:
121   case Triple::thumb:
122   case Triple::aarch64:
123     // These work properly with the clang driver, setting the expected
124     // defines such as _WIN32 etc.
125     break;
126   default:
127     // Other archs aren't set up for use with windows as target OS, (clang
128     // doesn't define e.g. _WIN32 etc), so set a reasonable default arch.
129     T.setArch(Triple::x86_64);
130     break;
131   }
132   T.setOS(Triple::Win32);
133   T.setVendor(Triple::PC);
134   T.setEnvironment(Triple::MSVC);
135   T.setObjectFormat(Triple::COFF);
136   return T.str();
137 }
138 
139 bool preprocess(StringRef Src, StringRef Dst, opt::InputArgList &InputArgs,
140                 const char *Argv0) {
141   std::string Clang;
142   if (InputArgs.hasArg(OPT__HASH_HASH_HASH)) {
143     Clang = "clang";
144   } else {
145     ErrorOr<std::string> ClangOrErr = findClang(Argv0);
146     if (ClangOrErr) {
147       Clang = *ClangOrErr;
148     } else {
149       errs() << "llvm-rc: Unable to find clang, skipping preprocessing."
150              << "\n";
151       errs() << "Pass -no-cpp to disable preprocessing. This will be an error "
152                 "in the future."
153              << "\n";
154       return false;
155     }
156   }
157   std::string PreprocTriple = getClangClTriple();
158 
159   SmallVector<StringRef, 8> Args = {
160       Clang, "--driver-mode=gcc", "-target", PreprocTriple, "-E",
161       "-xc", "-DRC_INVOKED",      Src,       "-o",          Dst};
162   if (InputArgs.hasArg(OPT_noinclude)) {
163 #ifdef _WIN32
164     ::_putenv("INCLUDE=");
165 #else
166     ::unsetenv("INCLUDE");
167 #endif
168   }
169   for (const auto *Arg :
170        InputArgs.filtered(OPT_includepath, OPT_define, OPT_undef)) {
171     switch (Arg->getOption().getID()) {
172     case OPT_includepath:
173       Args.push_back("-I");
174       break;
175     case OPT_define:
176       Args.push_back("-D");
177       break;
178     case OPT_undef:
179       Args.push_back("-U");
180       break;
181     }
182     Args.push_back(Arg->getValue());
183   }
184   if (InputArgs.hasArg(OPT__HASH_HASH_HASH) || InputArgs.hasArg(OPT_verbose)) {
185     for (const auto &A : Args) {
186       outs() << " ";
187       sys::printArg(outs(), A, InputArgs.hasArg(OPT__HASH_HASH_HASH));
188     }
189     outs() << "\n";
190     if (InputArgs.hasArg(OPT__HASH_HASH_HASH))
191       exit(0);
192   }
193   // The llvm Support classes don't handle reading from stdout of a child
194   // process; otherwise we could avoid using a temp file.
195   int Res = sys::ExecuteAndWait(Clang, Args);
196   if (Res) {
197     fatalError("llvm-rc: Preprocessing failed.");
198   }
199   return true;
200 }
201 
202 } // anonymous namespace
203 
204 int main(int Argc, const char **Argv) {
205   InitLLVM X(Argc, Argv);
206   ExitOnErr.setBanner("llvm-rc: ");
207 
208   RcOptTable T;
209   unsigned MAI, MAC;
210   const char **DashDash = std::find_if(
211       Argv + 1, Argv + Argc, [](StringRef Str) { return Str == "--"; });
212   ArrayRef<const char *> ArgsArr = makeArrayRef(Argv + 1, DashDash);
213 
214   opt::InputArgList InputArgs = T.ParseArgs(ArgsArr, MAI, MAC);
215 
216   // The tool prints nothing when invoked with no command-line arguments.
217   if (InputArgs.hasArg(OPT_help)) {
218     T.PrintHelp(outs(), "rc [options] file...", "Resource Converter", false);
219     return 0;
220   }
221 
222   const bool BeVerbose = InputArgs.hasArg(OPT_verbose);
223 
224   std::vector<std::string> InArgsInfo = InputArgs.getAllArgValues(OPT_INPUT);
225   if (DashDash != Argv + Argc)
226     InArgsInfo.insert(InArgsInfo.end(), DashDash + 1, Argv + Argc);
227   if (InArgsInfo.size() != 1) {
228     fatalError("Exactly one input file should be provided.");
229   }
230 
231   std::string PreprocessedFile = InArgsInfo[0];
232   if (!InputArgs.hasArg(OPT_no_preprocess)) {
233     std::string OutFile = createTempFile("preproc", "rc");
234     TempPreprocFile.setFile(OutFile);
235     if (preprocess(InArgsInfo[0], OutFile, InputArgs, Argv[0]))
236       PreprocessedFile = OutFile;
237   }
238 
239   // Read and tokenize the input file.
240   ErrorOr<std::unique_ptr<MemoryBuffer>> File =
241       MemoryBuffer::getFile(PreprocessedFile);
242   if (!File) {
243     fatalError("Error opening file '" + Twine(InArgsInfo[0]) +
244                "': " + File.getError().message());
245   }
246 
247   std::unique_ptr<MemoryBuffer> FileContents = std::move(*File);
248   StringRef Contents = FileContents->getBuffer();
249 
250   std::string FilteredContents = filterCppOutput(Contents);
251   std::vector<RCToken> Tokens = ExitOnErr(tokenizeRC(FilteredContents));
252 
253   if (BeVerbose) {
254     const Twine TokenNames[] = {
255 #define TOKEN(Name) #Name,
256 #define SHORT_TOKEN(Name, Ch) #Name,
257 #include "ResourceScriptTokenList.def"
258     };
259 
260     for (const RCToken &Token : Tokens) {
261       outs() << TokenNames[static_cast<int>(Token.kind())] << ": "
262              << Token.value();
263       if (Token.kind() == RCToken::Kind::Int)
264         outs() << "; int value = " << Token.intValue();
265 
266       outs() << "\n";
267     }
268   }
269 
270   WriterParams Params;
271   SmallString<128> InputFile(InArgsInfo[0]);
272   llvm::sys::fs::make_absolute(InputFile);
273   Params.InputFilePath = InputFile;
274   Params.Include = InputArgs.getAllArgValues(OPT_includepath);
275   Params.NoInclude = InputArgs.hasArg(OPT_noinclude);
276 
277   if (InputArgs.hasArg(OPT_codepage)) {
278     if (InputArgs.getLastArgValue(OPT_codepage)
279             .getAsInteger(10, Params.CodePage))
280       fatalError("Invalid code page: " +
281                  InputArgs.getLastArgValue(OPT_codepage));
282     switch (Params.CodePage) {
283     case CpAcp:
284     case CpWin1252:
285     case CpUtf8:
286       break;
287     default:
288       fatalError(
289           "Unsupported code page, only 0, 1252 and 65001 are supported!");
290     }
291   }
292 
293   std::unique_ptr<ResourceFileWriter> Visitor;
294   bool IsDryRun = InputArgs.hasArg(OPT_dry_run);
295 
296   if (!IsDryRun) {
297     auto OutArgsInfo = InputArgs.getAllArgValues(OPT_fileout);
298     if (OutArgsInfo.empty()) {
299       SmallString<128> OutputFile = InputFile;
300       llvm::sys::path::replace_extension(OutputFile, "res");
301       OutArgsInfo.push_back(std::string(OutputFile.str()));
302     }
303 
304     if (OutArgsInfo.size() != 1)
305       fatalError(
306           "No more than one output file should be provided (using /FO flag).");
307 
308     std::error_code EC;
309     auto FOut = std::make_unique<raw_fd_ostream>(
310         OutArgsInfo[0], EC, sys::fs::FA_Read | sys::fs::FA_Write);
311     if (EC)
312       fatalError("Error opening output file '" + OutArgsInfo[0] +
313                  "': " + EC.message());
314     Visitor = std::make_unique<ResourceFileWriter>(Params, std::move(FOut));
315     Visitor->AppendNull = InputArgs.hasArg(OPT_add_null);
316 
317     ExitOnErr(NullResource().visit(Visitor.get()));
318 
319     // Set the default language; choose en-US arbitrarily.
320     unsigned PrimaryLangId = 0x09, SubLangId = 0x01;
321     if (InputArgs.hasArg(OPT_lang_id)) {
322       unsigned LangId;
323       if (InputArgs.getLastArgValue(OPT_lang_id).getAsInteger(16, LangId))
324         fatalError("Invalid language id: " +
325                    InputArgs.getLastArgValue(OPT_lang_id));
326       PrimaryLangId = LangId & 0x3ff;
327       SubLangId = LangId >> 10;
328     }
329     ExitOnErr(LanguageResource(PrimaryLangId, SubLangId).visit(Visitor.get()));
330   }
331 
332   rc::RCParser Parser{std::move(Tokens)};
333   while (!Parser.isEof()) {
334     auto Resource = ExitOnErr(Parser.parseSingleResource());
335     if (BeVerbose)
336       Resource->log(outs());
337     if (!IsDryRun)
338       ExitOnErr(Resource->visit(Visitor.get()));
339   }
340 
341   // STRINGTABLE resources come at the very end.
342   if (!IsDryRun)
343     ExitOnErr(Visitor->dumpAllStringTables());
344 
345   return 0;
346 }
347