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