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