| 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/Config/llvm-config.h" |
| 21 | #include "llvm/Object/WindowsResource.h" |
| 22 | #include "llvm/Option/Arg.h" |
| 23 | #include "llvm/Option/ArgList.h" |
| 24 | #include "llvm/Option/OptTable.h" |
| 25 | #include "llvm/Support/CommandLine.h" |
| 26 | #include "llvm/Support/Error.h" |
| 27 | #include "llvm/Support/FileSystem.h" |
| 28 | #include "llvm/Support/FileUtilities.h" |
| 29 | #include "llvm/Support/LLVMDriver.h" |
| 30 | #include "llvm/Support/MemoryBuffer.h" |
| 31 | #include "llvm/Support/Path.h" |
| 32 | #include "llvm/Support/PrettyStackTrace.h" |
| 33 | #include "llvm/Support/Process.h" |
| 34 | #include "llvm/Support/Program.h" |
| 35 | #include "llvm/Support/Signals.h" |
| 36 | #include "llvm/Support/StringSaver.h" |
| 37 | #include "llvm/Support/raw_ostream.h" |
| 38 | #include "llvm/TargetParser/Host.h" |
| 39 | #include "llvm/TargetParser/Triple.h" |
| 40 | |
| 41 | #include <algorithm> |
| 42 | #include <system_error> |
| 43 | |
| 44 | using namespace llvm; |
| 45 | using namespace llvm::rc; |
| 46 | using namespace llvm::opt; |
| 47 | |
| 48 | namespace { |
| 49 | |
| 50 | // Input options tables. |
| 51 | |
| 52 | enum ID { |
| 53 | OPT_INVALID = 0, // This is not a correct option ID. |
| 54 | #define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__), |
| 55 | #include "Opts.inc" |
| 56 | #undef OPTION |
| 57 | }; |
| 58 | |
| 59 | namespace rc_opt { |
| 60 | #define OPTTABLE_STR_TABLE_CODE |
| 61 | #include "Opts.inc" |
| 62 | #undef OPTTABLE_STR_TABLE_CODE |
| 63 | |
| 64 | #define OPTTABLE_PREFIXES_TABLE_CODE |
| 65 | #include "Opts.inc" |
| 66 | #undef OPTTABLE_PREFIXES_TABLE_CODE |
| 67 | |
| 68 | static constexpr opt::OptTable::Info InfoTable[] = { |
| 69 | #define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__), |
| 70 | #include "Opts.inc" |
| 71 | #undef OPTION |
| 72 | }; |
| 73 | } // namespace rc_opt |
| 74 | |
| 75 | class RcOptTable : public opt::GenericOptTable { |
| 76 | public: |
| 77 | RcOptTable() |
| 78 | : GenericOptTable(rc_opt::OptionStrTable, rc_opt::OptionPrefixesTable, |
| 79 | rc_opt::InfoTable, |
| 80 | /* IgnoreCase = */ true) {} |
| 81 | }; |
| 82 | |
| 83 | enum Windres_ID { |
| 84 | WINDRES_INVALID = 0, // This is not a correct option ID. |
| 85 | #define OPTION(...) LLVM_MAKE_OPT_ID_WITH_ID_PREFIX(WINDRES_, __VA_ARGS__), |
| 86 | #include "WindresOpts.inc" |
| 87 | #undef OPTION |
| 88 | }; |
| 89 | |
| 90 | namespace windres_opt { |
| 91 | #define OPTTABLE_STR_TABLE_CODE |
| 92 | #include "WindresOpts.inc" |
| 93 | #undef OPTTABLE_STR_TABLE_CODE |
| 94 | |
| 95 | #define OPTTABLE_PREFIXES_TABLE_CODE |
| 96 | #include "WindresOpts.inc" |
| 97 | #undef OPTTABLE_PREFIXES_TABLE_CODE |
| 98 | |
| 99 | static constexpr opt::OptTable::Info InfoTable[] = { |
| 100 | #define OPTION(...) \ |
| 101 | LLVM_CONSTRUCT_OPT_INFO_WITH_ID_PREFIX(WINDRES_, __VA_ARGS__), |
| 102 | #include "WindresOpts.inc" |
| 103 | #undef OPTION |
| 104 | }; |
| 105 | } // namespace windres_opt |
| 106 | |
| 107 | class WindresOptTable : public opt::GenericOptTable { |
| 108 | public: |
| 109 | WindresOptTable() |
| 110 | : GenericOptTable(windres_opt::OptionStrTable, |
| 111 | windres_opt::OptionPrefixesTable, |
| 112 | windres_opt::InfoTable, |
| 113 | /* IgnoreCase = */ false) {} |
| 114 | }; |
| 115 | |
| 116 | static ExitOnError ExitOnErr; |
| 117 | static FileRemover TempPreprocFile; |
| 118 | static FileRemover TempResFile; |
| 119 | |
| 120 | [[noreturn]] static void fatalError(const Twine &Message) { |
| 121 | errs() << Message << "\n" ; |
| 122 | exit(status: 1); |
| 123 | } |
| 124 | |
| 125 | std::string createTempFile(const Twine &Prefix, StringRef Suffix) { |
| 126 | std::error_code EC; |
| 127 | SmallString<128> FileName; |
| 128 | if ((EC = sys::fs::createTemporaryFile(Prefix, Suffix, ResultPath&: FileName))) |
| 129 | fatalError(Message: "Unable to create temp file: " + EC.message()); |
| 130 | return static_cast<std::string>(FileName); |
| 131 | } |
| 132 | |
| 133 | ErrorOr<std::string> findClang(const char *Argv0, StringRef Triple) { |
| 134 | // This just needs to be some symbol in the binary. |
| 135 | void *P = (void*) (intptr_t) findClang; |
| 136 | std::string MainExecPath = llvm::sys::fs::getMainExecutable(argv0: Argv0, MainExecAddr: P); |
| 137 | if (MainExecPath.empty()) |
| 138 | MainExecPath = Argv0; |
| 139 | |
| 140 | ErrorOr<std::string> Path = std::error_code(); |
| 141 | std::string TargetClang = (Triple + "-clang" ).str(); |
| 142 | std::string VersionedClang = ("clang-" + Twine(LLVM_VERSION_MAJOR)).str(); |
| 143 | for (const auto *Name : |
| 144 | {TargetClang.c_str(), VersionedClang.c_str(), "clang" , "clang-cl" }) { |
| 145 | for (const StringRef Parent : |
| 146 | {llvm::sys::path::parent_path(path: MainExecPath), |
| 147 | llvm::sys::path::parent_path(path: Argv0)}) { |
| 148 | // Look for various versions of "clang" first in the MainExecPath parent |
| 149 | // directory and then in the argv[0] parent directory. |
| 150 | // On Windows (but not Unix) argv[0] is overwritten with the eqiuvalent |
| 151 | // of MainExecPath by InitLLVM. |
| 152 | Path = sys::findProgramByName(Name, Paths: Parent); |
| 153 | if (Path) |
| 154 | return Path; |
| 155 | } |
| 156 | } |
| 157 | |
| 158 | // If no parent directory known, or not found there, look everywhere in PATH |
| 159 | for (const auto *Name : {"clang" , "clang-cl" }) { |
| 160 | Path = sys::findProgramByName(Name); |
| 161 | if (Path) |
| 162 | return Path; |
| 163 | } |
| 164 | return Path; |
| 165 | } |
| 166 | |
| 167 | bool isUsableArch(Triple::ArchType Arch) { |
| 168 | switch (Arch) { |
| 169 | case Triple::x86: |
| 170 | case Triple::x86_64: |
| 171 | case Triple::arm: |
| 172 | case Triple::thumb: |
| 173 | case Triple::aarch64: |
| 174 | // These work properly with the clang driver, setting the expected |
| 175 | // defines such as _WIN32 etc. |
| 176 | return true; |
| 177 | default: |
| 178 | // Other archs aren't set up for use with windows as target OS, (clang |
| 179 | // doesn't define e.g. _WIN32 etc), so with them we need to set a |
| 180 | // different default arch. |
| 181 | return false; |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | Triple::ArchType getDefaultFallbackArch() { |
| 186 | return Triple::x86_64; |
| 187 | } |
| 188 | |
| 189 | std::string getClangClTriple() { |
| 190 | Triple T(sys::getDefaultTargetTriple()); |
| 191 | if (!isUsableArch(Arch: T.getArch())) |
| 192 | T.setArch(Kind: getDefaultFallbackArch()); |
| 193 | T.setOS(Triple::Win32); |
| 194 | T.setVendor(Triple::PC); |
| 195 | T.setEnvironment(Triple::MSVC); |
| 196 | T.setObjectFormat(Triple::COFF); |
| 197 | return T.str(); |
| 198 | } |
| 199 | |
| 200 | std::string getMingwTriple() { |
| 201 | Triple T(sys::getDefaultTargetTriple()); |
| 202 | if (!isUsableArch(Arch: T.getArch())) |
| 203 | T.setArch(Kind: getDefaultFallbackArch()); |
| 204 | if (T.isWindowsGNUEnvironment()) |
| 205 | return T.str(); |
| 206 | // Write out the literal form of the vendor/env here, instead of |
| 207 | // constructing them with enum values (which end up with them in |
| 208 | // normalized form). The literal form of the triple can matter for |
| 209 | // finding include files. |
| 210 | return (Twine(T.getArchName()) + "-w64-mingw32" ).str(); |
| 211 | } |
| 212 | |
| 213 | enum Format { Rc, Res, Coff, Unknown }; |
| 214 | |
| 215 | struct RcOptions { |
| 216 | bool Preprocess = true; |
| 217 | bool PrintCmdAndExit = false; |
| 218 | std::string Triple; |
| 219 | std::optional<std::string> Preprocessor; |
| 220 | std::vector<std::string> PreprocessArgs; |
| 221 | |
| 222 | std::string InputFile; |
| 223 | Format InputFormat = Rc; |
| 224 | std::string OutputFile; |
| 225 | Format OutputFormat = Res; |
| 226 | |
| 227 | bool IsWindres = false; |
| 228 | bool BeVerbose = false; |
| 229 | WriterParams Params; |
| 230 | bool AppendNull = false; |
| 231 | bool IsDryRun = false; |
| 232 | // Set the default language; choose en-US arbitrarily. |
| 233 | unsigned LangId = (/*PrimaryLangId*/ 0x09) | (/*SubLangId*/ 0x01 << 10); |
| 234 | }; |
| 235 | |
| 236 | void preprocess(StringRef Src, StringRef Dst, const RcOptions &Opts, |
| 237 | const char *Argv0) { |
| 238 | std::string Clang; |
| 239 | if (Opts.PrintCmdAndExit || Opts.Preprocessor) { |
| 240 | Clang = "clang" ; |
| 241 | } else { |
| 242 | ErrorOr<std::string> ClangOrErr = findClang(Argv0, Triple: Opts.Triple); |
| 243 | if (ClangOrErr) { |
| 244 | Clang = *ClangOrErr; |
| 245 | } else { |
| 246 | errs() << "llvm-rc: Unable to find clang for preprocessing." |
| 247 | << "\n" ; |
| 248 | StringRef OptionName = |
| 249 | Opts.IsWindres ? "--no-preprocess" : "-no-preprocess" ; |
| 250 | errs() << "Pass " << OptionName << " to disable preprocessing.\n" ; |
| 251 | fatalError(Message: "llvm-rc: Unable to preprocess." ); |
| 252 | } |
| 253 | } |
| 254 | |
| 255 | SmallVector<StringRef, 8> Args = { |
| 256 | Clang, "--driver-mode=gcc" , "-target" , Opts.Triple, "-E" , |
| 257 | "-xc" , "-DRC_INVOKED" }; |
| 258 | std::string PreprocessorExecutable; |
| 259 | if (Opts.Preprocessor) { |
| 260 | Args.clear(); |
| 261 | Args.push_back(Elt: *Opts.Preprocessor); |
| 262 | if (!sys::fs::can_execute(Path: Args[0])) { |
| 263 | if (auto P = sys::findProgramByName(Name: Args[0])) { |
| 264 | PreprocessorExecutable = *P; |
| 265 | Args[0] = PreprocessorExecutable; |
| 266 | } |
| 267 | } |
| 268 | } |
| 269 | llvm::append_range(C&: Args, R: Opts.PreprocessArgs); |
| 270 | Args.push_back(Elt: Src); |
| 271 | Args.push_back(Elt: "-o" ); |
| 272 | Args.push_back(Elt: Dst); |
| 273 | if (Opts.PrintCmdAndExit || Opts.BeVerbose) { |
| 274 | for (const auto &A : Args) { |
| 275 | outs() << " " ; |
| 276 | sys::printArg(OS&: outs(), Arg: A, Quote: Opts.PrintCmdAndExit); |
| 277 | } |
| 278 | outs() << "\n" ; |
| 279 | if (Opts.PrintCmdAndExit) |
| 280 | exit(status: 0); |
| 281 | } |
| 282 | // The llvm Support classes don't handle reading from stdout of a child |
| 283 | // process; otherwise we could avoid using a temp file. |
| 284 | std::string ErrMsg; |
| 285 | int Res = |
| 286 | sys::ExecuteAndWait(Program: Args[0], Args, /*Env=*/std::nullopt, /*Redirects=*/{}, |
| 287 | /*SecondsToWait=*/0, /*MemoryLimit=*/0, ErrMsg: &ErrMsg); |
| 288 | if (Res) { |
| 289 | if (!ErrMsg.empty()) |
| 290 | fatalError(Message: "llvm-rc: Preprocessing failed: " + ErrMsg); |
| 291 | else |
| 292 | fatalError(Message: "llvm-rc: Preprocessing failed." ); |
| 293 | } |
| 294 | } |
| 295 | |
| 296 | static std::pair<bool, std::string> isWindres(llvm::StringRef Argv0) { |
| 297 | StringRef ProgName = llvm::sys::path::stem(path: Argv0); |
| 298 | // x86_64-w64-mingw32-windres -> x86_64-w64-mingw32, windres |
| 299 | // llvm-rc -> "", llvm-rc |
| 300 | // aarch64-w64-mingw32-llvm-windres-10.exe -> aarch64-w64-mingw32, llvm-windres |
| 301 | ProgName = ProgName.rtrim(Chars: "0123456789.-" ); |
| 302 | if (!ProgName.consume_back_insensitive(Suffix: "windres" )) |
| 303 | return std::make_pair<bool, std::string>(x: false, y: "" ); |
| 304 | ProgName.consume_back_insensitive(Suffix: "llvm-" ); |
| 305 | ProgName.consume_back_insensitive(Suffix: "-" ); |
| 306 | return std::make_pair<bool, std::string>(x: true, y: ProgName.str()); |
| 307 | } |
| 308 | |
| 309 | Format parseFormat(StringRef S) { |
| 310 | Format F = StringSwitch<Format>(S.lower()) |
| 311 | .Case(S: "rc" , Value: Rc) |
| 312 | .Case(S: "res" , Value: Res) |
| 313 | .Case(S: "coff" , Value: Coff) |
| 314 | .Default(Value: Unknown); |
| 315 | if (F == Unknown) |
| 316 | fatalError(Message: "Unable to parse '" + Twine(S) + "' as a format" ); |
| 317 | return F; |
| 318 | } |
| 319 | |
| 320 | void deduceFormat(Format &Dest, StringRef File) { |
| 321 | Format F = StringSwitch<Format>(sys::path::extension(path: File.lower())) |
| 322 | .Case(S: ".rc" , Value: Rc) |
| 323 | .Case(S: ".res" , Value: Res) |
| 324 | .Case(S: ".o" , Value: Coff) |
| 325 | .Case(S: ".obj" , Value: Coff) |
| 326 | .Default(Value: Unknown); |
| 327 | if (F != Unknown) |
| 328 | Dest = F; |
| 329 | } |
| 330 | |
| 331 | std::string unescape(StringRef S) { |
| 332 | std::string Out; |
| 333 | Out.reserve(res_arg: S.size()); |
| 334 | for (int I = 0, E = S.size(); I < E; I++) { |
| 335 | if (S[I] == '\\') { |
| 336 | if (I + 1 < E) |
| 337 | Out.push_back(c: S[++I]); |
| 338 | else |
| 339 | fatalError(Message: "Unterminated escape" ); |
| 340 | continue; |
| 341 | } else if (S[I] == '"') { |
| 342 | // This eats an individual unescaped quote, like a shell would do. |
| 343 | continue; |
| 344 | } |
| 345 | Out.push_back(c: S[I]); |
| 346 | } |
| 347 | return Out; |
| 348 | } |
| 349 | |
| 350 | RcOptions parseWindresOptions(ArrayRef<const char *> ArgsArr, |
| 351 | ArrayRef<const char *> InputArgsArray, |
| 352 | std::string Prefix) { |
| 353 | WindresOptTable T; |
| 354 | RcOptions Opts; |
| 355 | unsigned MAI, MAC; |
| 356 | opt::InputArgList InputArgs = T.ParseArgs(Args: ArgsArr, MissingArgIndex&: MAI, MissingArgCount&: MAC); |
| 357 | |
| 358 | Opts.IsWindres = true; |
| 359 | |
| 360 | // The tool prints nothing when invoked with no command-line arguments. |
| 361 | if (InputArgs.hasArg(Ids: WINDRES_help)) { |
| 362 | T.printHelp(OS&: outs(), Usage: "windres [options] file..." , |
| 363 | Title: "LLVM windres (GNU windres compatible)" , ShowHidden: false, ShowAllAliases: true); |
| 364 | exit(status: 0); |
| 365 | } |
| 366 | |
| 367 | if (InputArgs.hasArg(Ids: WINDRES_version)) { |
| 368 | outs() << "llvm-windres, compatible with GNU windres\n" ; |
| 369 | cl::PrintVersionMessage(); |
| 370 | exit(status: 0); |
| 371 | } |
| 372 | |
| 373 | std::vector<std::string> FileArgs = InputArgs.getAllArgValues(Id: WINDRES_INPUT); |
| 374 | llvm::append_range(C&: FileArgs, R&: InputArgsArray); |
| 375 | |
| 376 | if (InputArgs.hasArg(Ids: WINDRES_input)) { |
| 377 | Opts.InputFile = InputArgs.getLastArgValue(Id: WINDRES_input).str(); |
| 378 | } else if (!FileArgs.empty()) { |
| 379 | Opts.InputFile = FileArgs.front(); |
| 380 | FileArgs.erase(position: FileArgs.begin()); |
| 381 | } else { |
| 382 | // TODO: GNU windres takes input on stdin in this case. |
| 383 | fatalError(Message: "Missing input file" ); |
| 384 | } |
| 385 | |
| 386 | if (InputArgs.hasArg(Ids: WINDRES_output)) { |
| 387 | Opts.OutputFile = InputArgs.getLastArgValue(Id: WINDRES_output).str(); |
| 388 | } else if (!FileArgs.empty()) { |
| 389 | Opts.OutputFile = FileArgs.front(); |
| 390 | FileArgs.erase(position: FileArgs.begin()); |
| 391 | } else { |
| 392 | // TODO: GNU windres writes output in rc form to stdout in this case. |
| 393 | fatalError(Message: "Missing output file" ); |
| 394 | } |
| 395 | |
| 396 | if (InputArgs.hasArg(Ids: WINDRES_input_format)) { |
| 397 | Opts.InputFormat = |
| 398 | parseFormat(S: InputArgs.getLastArgValue(Id: WINDRES_input_format)); |
| 399 | } else { |
| 400 | deduceFormat(Dest&: Opts.InputFormat, File: Opts.InputFile); |
| 401 | } |
| 402 | if (Opts.InputFormat == Coff) |
| 403 | fatalError(Message: "Unsupported input format" ); |
| 404 | |
| 405 | if (InputArgs.hasArg(Ids: WINDRES_output_format)) { |
| 406 | Opts.OutputFormat = |
| 407 | parseFormat(S: InputArgs.getLastArgValue(Id: WINDRES_output_format)); |
| 408 | } else { |
| 409 | // The default in windres differs from the default in RcOptions |
| 410 | Opts.OutputFormat = Coff; |
| 411 | deduceFormat(Dest&: Opts.OutputFormat, File: Opts.OutputFile); |
| 412 | } |
| 413 | if (Opts.OutputFormat == Rc) |
| 414 | fatalError(Message: "Unsupported output format" ); |
| 415 | if (Opts.InputFormat == Opts.OutputFormat) { |
| 416 | outs() << "Nothing to do.\n" ; |
| 417 | exit(status: 0); |
| 418 | } |
| 419 | |
| 420 | Opts.PrintCmdAndExit = InputArgs.hasArg(Ids: WINDRES__HASH_HASH_HASH); |
| 421 | Opts.Preprocess = !InputArgs.hasArg(Ids: WINDRES_no_preprocess); |
| 422 | Triple TT(Prefix); |
| 423 | if (InputArgs.hasArg(Ids: WINDRES_target)) { |
| 424 | StringRef Value = InputArgs.getLastArgValue(Id: WINDRES_target); |
| 425 | if (Value == "pe-i386" ) |
| 426 | Opts.Triple = "i686-w64-mingw32" ; |
| 427 | else if (Value == "pe-x86-64" ) |
| 428 | Opts.Triple = "x86_64-w64-mingw32" ; |
| 429 | else |
| 430 | // Implicit extension; if the --target value isn't one of the known |
| 431 | // BFD targets, allow setting the full triple string via this instead. |
| 432 | Opts.Triple = Value.str(); |
| 433 | } else if (TT.getArch() != Triple::UnknownArch) |
| 434 | Opts.Triple = Prefix; |
| 435 | else |
| 436 | Opts.Triple = getMingwTriple(); |
| 437 | |
| 438 | for (const auto *Arg : |
| 439 | InputArgs.filtered(Ids: WINDRES_include_dir, Ids: WINDRES_define, Ids: WINDRES_undef, |
| 440 | Ids: WINDRES_preprocessor_arg)) { |
| 441 | // GNU windres passes the arguments almost as-is on to popen() (it only |
| 442 | // backslash escapes spaces in the arguments), where a shell would |
| 443 | // unescape backslash escapes for quotes and similar. This means that |
| 444 | // when calling GNU windres, callers need to double escape chars like |
| 445 | // quotes, e.g. as -DSTRING=\\\"1.2.3\\\". |
| 446 | // |
| 447 | // Exactly how the arguments are interpreted depends on the platform |
| 448 | // though - but the cases where this matters (where callers would have |
| 449 | // done this double escaping) probably is confined to cases like these |
| 450 | // quoted string defines, and those happen to work the same across unix |
| 451 | // and windows. |
| 452 | // |
| 453 | // If GNU windres is executed with --use-temp-file, it doesn't use |
| 454 | // popen() to invoke the preprocessor, but uses another function which |
| 455 | // actually preserves tricky characters better. To mimic this behaviour, |
| 456 | // don't unescape arguments here. |
| 457 | std::string Value = Arg->getValue(); |
| 458 | if (!InputArgs.hasArg(Ids: WINDRES_use_temp_file)) |
| 459 | Value = unescape(S: Value); |
| 460 | switch (Arg->getOption().getID()) { |
| 461 | case WINDRES_include_dir: |
| 462 | // Technically, these are handled the same way as e.g. defines, but |
| 463 | // the way we consistently unescape the unix way breaks windows paths |
| 464 | // with single backslashes. Alternatively, our unescape function would |
| 465 | // need to mimic the platform specific command line parsing/unescaping |
| 466 | // logic. |
| 467 | Opts.Params.Include.push_back(x: Arg->getValue()); |
| 468 | Opts.PreprocessArgs.push_back(x: "-I" ); |
| 469 | Opts.PreprocessArgs.push_back(x: Arg->getValue()); |
| 470 | break; |
| 471 | case WINDRES_define: |
| 472 | Opts.PreprocessArgs.push_back(x: "-D" ); |
| 473 | Opts.PreprocessArgs.push_back(x: Value); |
| 474 | break; |
| 475 | case WINDRES_undef: |
| 476 | Opts.PreprocessArgs.push_back(x: "-U" ); |
| 477 | Opts.PreprocessArgs.push_back(x: Value); |
| 478 | break; |
| 479 | case WINDRES_preprocessor_arg: |
| 480 | Opts.PreprocessArgs.push_back(x: Value); |
| 481 | break; |
| 482 | } |
| 483 | } |
| 484 | if (InputArgs.hasArg(Ids: WINDRES_preprocessor)) |
| 485 | Opts.Preprocessor = InputArgs.getLastArgValue(Id: WINDRES_preprocessor); |
| 486 | |
| 487 | Opts.Params.CodePage = CpWin1252; // Different default |
| 488 | if (InputArgs.hasArg(Ids: WINDRES_codepage)) { |
| 489 | if (InputArgs.getLastArgValue(Id: WINDRES_codepage) |
| 490 | .getAsInteger(Radix: 0, Result&: Opts.Params.CodePage)) |
| 491 | fatalError(Message: "Invalid code page: " + |
| 492 | InputArgs.getLastArgValue(Id: WINDRES_codepage)); |
| 493 | } |
| 494 | if (InputArgs.hasArg(Ids: WINDRES_language)) { |
| 495 | StringRef Val = InputArgs.getLastArgValue(Id: WINDRES_language); |
| 496 | Val.consume_front_insensitive(Prefix: "0x" ); |
| 497 | if (Val.getAsInteger(Radix: 16, Result&: Opts.LangId)) |
| 498 | fatalError(Message: "Invalid language id: " + |
| 499 | InputArgs.getLastArgValue(Id: WINDRES_language)); |
| 500 | } |
| 501 | |
| 502 | Opts.BeVerbose = InputArgs.hasArg(Ids: WINDRES_verbose); |
| 503 | |
| 504 | return Opts; |
| 505 | } |
| 506 | |
| 507 | RcOptions parseRcOptions(ArrayRef<const char *> ArgsArr, |
| 508 | ArrayRef<const char *> InputArgsArray) { |
| 509 | RcOptTable T; |
| 510 | RcOptions Opts; |
| 511 | unsigned MAI, MAC; |
| 512 | opt::InputArgList InputArgs = T.ParseArgs(Args: ArgsArr, MissingArgIndex&: MAI, MissingArgCount&: MAC); |
| 513 | |
| 514 | // The tool prints nothing when invoked with no command-line arguments. |
| 515 | if (InputArgs.hasArg(Ids: OPT_help)) { |
| 516 | T.printHelp(OS&: outs(), Usage: "llvm-rc [options] file..." , Title: "LLVM Resource Converter" , |
| 517 | ShowHidden: false); |
| 518 | exit(status: 0); |
| 519 | } |
| 520 | |
| 521 | std::vector<std::string> InArgsInfo = InputArgs.getAllArgValues(Id: OPT_INPUT); |
| 522 | llvm::append_range(C&: InArgsInfo, R&: InputArgsArray); |
| 523 | if (InArgsInfo.size() != 1) { |
| 524 | fatalError(Message: "Exactly one input file should be provided." ); |
| 525 | } |
| 526 | |
| 527 | Opts.PrintCmdAndExit = InputArgs.hasArg(Ids: OPT__HASH_HASH_HASH); |
| 528 | Opts.Triple = getClangClTriple(); |
| 529 | for (const auto *Arg : |
| 530 | InputArgs.filtered(Ids: OPT_includepath, Ids: OPT_define, Ids: OPT_undef)) { |
| 531 | switch (Arg->getOption().getID()) { |
| 532 | case OPT_includepath: |
| 533 | Opts.PreprocessArgs.push_back(x: "-I" ); |
| 534 | break; |
| 535 | case OPT_define: |
| 536 | Opts.PreprocessArgs.push_back(x: "-D" ); |
| 537 | break; |
| 538 | case OPT_undef: |
| 539 | Opts.PreprocessArgs.push_back(x: "-U" ); |
| 540 | break; |
| 541 | } |
| 542 | Opts.PreprocessArgs.push_back(x: Arg->getValue()); |
| 543 | } |
| 544 | |
| 545 | Opts.InputFile = InArgsInfo[0]; |
| 546 | Opts.BeVerbose = InputArgs.hasArg(Ids: OPT_verbose); |
| 547 | Opts.Preprocess = !InputArgs.hasArg(Ids: OPT_no_preprocess); |
| 548 | Opts.Params.Include = InputArgs.getAllArgValues(Id: OPT_includepath); |
| 549 | Opts.Params.NoInclude = InputArgs.hasArg(Ids: OPT_noinclude); |
| 550 | if (Opts.Params.NoInclude) { |
| 551 | // Clear the INLCUDE variable for the external preprocessor |
| 552 | #ifdef _WIN32 |
| 553 | ::_putenv("INCLUDE=" ); |
| 554 | #else |
| 555 | ::unsetenv(name: "INCLUDE" ); |
| 556 | #endif |
| 557 | } |
| 558 | if (InputArgs.hasArg(Ids: OPT_codepage)) { |
| 559 | if (InputArgs.getLastArgValue(Id: OPT_codepage) |
| 560 | .getAsInteger(Radix: 10, Result&: Opts.Params.CodePage)) |
| 561 | fatalError(Message: "Invalid code page: " + |
| 562 | InputArgs.getLastArgValue(Id: OPT_codepage)); |
| 563 | } |
| 564 | Opts.IsDryRun = InputArgs.hasArg(Ids: OPT_dry_run); |
| 565 | auto OutArgsInfo = InputArgs.getAllArgValues(Id: OPT_fileout); |
| 566 | if (OutArgsInfo.empty()) { |
| 567 | SmallString<128> OutputFile(Opts.InputFile); |
| 568 | llvm::sys::fs::make_absolute(path&: OutputFile); |
| 569 | llvm::sys::path::replace_extension(path&: OutputFile, extension: "res" ); |
| 570 | OutArgsInfo.push_back(x: std::string(OutputFile)); |
| 571 | } |
| 572 | if (!Opts.IsDryRun) { |
| 573 | if (OutArgsInfo.size() != 1) |
| 574 | fatalError( |
| 575 | Message: "No more than one output file should be provided (using /FO flag)." ); |
| 576 | Opts.OutputFile = OutArgsInfo[0]; |
| 577 | } |
| 578 | Opts.AppendNull = InputArgs.hasArg(Ids: OPT_add_null); |
| 579 | if (InputArgs.hasArg(Ids: OPT_lang_id)) { |
| 580 | StringRef Val = InputArgs.getLastArgValue(Id: OPT_lang_id); |
| 581 | Val.consume_front_insensitive(Prefix: "0x" ); |
| 582 | if (Val.getAsInteger(Radix: 16, Result&: Opts.LangId)) |
| 583 | fatalError(Message: "Invalid language id: " + |
| 584 | InputArgs.getLastArgValue(Id: OPT_lang_id)); |
| 585 | } |
| 586 | return Opts; |
| 587 | } |
| 588 | |
| 589 | RcOptions getOptions(const char *Argv0, ArrayRef<const char *> ArgsArr, |
| 590 | ArrayRef<const char *> InputArgs) { |
| 591 | std::string Prefix; |
| 592 | bool IsWindres; |
| 593 | std::tie(args&: IsWindres, args&: Prefix) = isWindres(Argv0); |
| 594 | if (IsWindres) |
| 595 | return parseWindresOptions(ArgsArr, InputArgsArray: InputArgs, Prefix); |
| 596 | else |
| 597 | return parseRcOptions(ArgsArr, InputArgsArray: InputArgs); |
| 598 | } |
| 599 | |
| 600 | void doRc(std::string Src, std::string Dest, RcOptions &Opts, |
| 601 | const char *Argv0) { |
| 602 | std::string PreprocessedFile = Src; |
| 603 | if (Opts.Preprocess) { |
| 604 | std::string OutFile = createTempFile(Prefix: "preproc" , Suffix: "rc" ); |
| 605 | TempPreprocFile.setFile(filename: OutFile); |
| 606 | preprocess(Src, Dst: OutFile, Opts, Argv0); |
| 607 | PreprocessedFile = OutFile; |
| 608 | } |
| 609 | |
| 610 | // Read and tokenize the input file. |
| 611 | ErrorOr<std::unique_ptr<MemoryBuffer>> File = |
| 612 | MemoryBuffer::getFile(Filename: PreprocessedFile, /*IsText=*/true); |
| 613 | if (!File) { |
| 614 | fatalError(Message: "Error opening file '" + Twine(PreprocessedFile) + |
| 615 | "': " + File.getError().message()); |
| 616 | } |
| 617 | |
| 618 | std::unique_ptr<MemoryBuffer> FileContents = std::move(*File); |
| 619 | StringRef Contents = FileContents->getBuffer(); |
| 620 | |
| 621 | std::string FilteredContents = filterCppOutput(Input: Contents); |
| 622 | std::vector<RCToken> Tokens = ExitOnErr(tokenizeRC(Input: FilteredContents)); |
| 623 | |
| 624 | if (Opts.BeVerbose) { |
| 625 | const Twine TokenNames[] = { |
| 626 | #define TOKEN(Name) #Name, |
| 627 | #define SHORT_TOKEN(Name, Ch) #Name, |
| 628 | #include "ResourceScriptTokenList.def" |
| 629 | }; |
| 630 | |
| 631 | for (const RCToken &Token : Tokens) { |
| 632 | outs() << TokenNames[static_cast<int>(Token.kind())] << ": " |
| 633 | << Token.value(); |
| 634 | if (Token.kind() == RCToken::Kind::Int) |
| 635 | outs() << "; int value = " << Token.intValue(); |
| 636 | |
| 637 | outs() << "\n" ; |
| 638 | } |
| 639 | } |
| 640 | |
| 641 | WriterParams &Params = Opts.Params; |
| 642 | SmallString<128> InputFile(Src); |
| 643 | llvm::sys::fs::make_absolute(path&: InputFile); |
| 644 | Params.InputFilePath = InputFile; |
| 645 | |
| 646 | switch (Params.CodePage) { |
| 647 | case CpAcp: |
| 648 | case CpWin1252: |
| 649 | case CpUtf8: |
| 650 | break; |
| 651 | default: |
| 652 | fatalError(Message: "Unsupported code page, only 0, 1252 and 65001 are supported!" ); |
| 653 | } |
| 654 | |
| 655 | std::unique_ptr<ResourceFileWriter> Visitor; |
| 656 | |
| 657 | if (!Opts.IsDryRun) { |
| 658 | std::error_code EC; |
| 659 | auto FOut = std::make_unique<raw_fd_ostream>( |
| 660 | args&: Dest, args&: EC, args: sys::fs::FA_Read | sys::fs::FA_Write); |
| 661 | if (EC) |
| 662 | fatalError(Message: "Error opening output file '" + Dest + "': " + EC.message()); |
| 663 | Visitor = std::make_unique<ResourceFileWriter>(args&: Params, args: std::move(FOut)); |
| 664 | Visitor->AppendNull = Opts.AppendNull; |
| 665 | |
| 666 | ExitOnErr(NullResource().visit(V: Visitor.get())); |
| 667 | |
| 668 | unsigned PrimaryLangId = Opts.LangId & 0x3ff; |
| 669 | unsigned SubLangId = Opts.LangId >> 10; |
| 670 | ExitOnErr(LanguageResource(PrimaryLangId, SubLangId).visit(V: Visitor.get())); |
| 671 | } |
| 672 | |
| 673 | rc::RCParser Parser{std::move(Tokens)}; |
| 674 | while (!Parser.isEof()) { |
| 675 | auto Resource = ExitOnErr(Parser.parseSingleResource()); |
| 676 | if (Opts.BeVerbose) |
| 677 | Resource->log(OS&: outs()); |
| 678 | if (!Opts.IsDryRun) |
| 679 | ExitOnErr(Resource->visit(Visitor.get())); |
| 680 | } |
| 681 | |
| 682 | // STRINGTABLE resources come at the very end. |
| 683 | if (!Opts.IsDryRun) |
| 684 | ExitOnErr(Visitor->dumpAllStringTables()); |
| 685 | } |
| 686 | |
| 687 | void doCvtres(std::string Src, std::string Dest, std::string TargetTriple) { |
| 688 | object::WindowsResourceParser Parser; |
| 689 | |
| 690 | ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr = |
| 691 | MemoryBuffer::getFile(Filename: Src, /*IsText=*/true); |
| 692 | if (!BufferOrErr) |
| 693 | fatalError(Message: "Error opening file '" + Twine(Src) + |
| 694 | "': " + BufferOrErr.getError().message()); |
| 695 | std::unique_ptr<MemoryBuffer> &Buffer = BufferOrErr.get(); |
| 696 | std::unique_ptr<object::WindowsResource> Binary = |
| 697 | ExitOnErr(object::WindowsResource::createWindowsResource( |
| 698 | Source: Buffer->getMemBufferRef())); |
| 699 | |
| 700 | std::vector<std::string> Duplicates; |
| 701 | ExitOnErr(Parser.parse(WR: Binary.get(), Duplicates)); |
| 702 | for (const auto &DupeDiag : Duplicates) |
| 703 | fatalError(Message: "Duplicate resources: " + DupeDiag); |
| 704 | |
| 705 | Triple T(TargetTriple); |
| 706 | COFF::MachineTypes MachineType; |
| 707 | switch (T.getArch()) { |
| 708 | case Triple::x86: |
| 709 | MachineType = COFF::IMAGE_FILE_MACHINE_I386; |
| 710 | break; |
| 711 | case Triple::x86_64: |
| 712 | MachineType = COFF::IMAGE_FILE_MACHINE_AMD64; |
| 713 | break; |
| 714 | case Triple::arm: |
| 715 | case Triple::thumb: |
| 716 | MachineType = COFF::IMAGE_FILE_MACHINE_ARMNT; |
| 717 | break; |
| 718 | case Triple::aarch64: |
| 719 | if (T.isWindowsArm64EC()) |
| 720 | MachineType = COFF::IMAGE_FILE_MACHINE_ARM64EC; |
| 721 | else |
| 722 | MachineType = COFF::IMAGE_FILE_MACHINE_ARM64; |
| 723 | break; |
| 724 | default: |
| 725 | fatalError(Message: "Unsupported architecture in target '" + Twine(TargetTriple) + |
| 726 | "'" ); |
| 727 | } |
| 728 | |
| 729 | std::unique_ptr<MemoryBuffer> OutputBuffer = |
| 730 | ExitOnErr(object::writeWindowsResourceCOFF(MachineType, Parser, |
| 731 | /*DateTimeStamp*/ TimeDateStamp: 0)); |
| 732 | std::unique_ptr<FileOutputBuffer> FileBuffer = |
| 733 | ExitOnErr(FileOutputBuffer::create(FilePath: Dest, Size: OutputBuffer->getBufferSize())); |
| 734 | std::copy(first: OutputBuffer->getBufferStart(), last: OutputBuffer->getBufferEnd(), |
| 735 | result: FileBuffer->getBufferStart()); |
| 736 | ExitOnErr(FileBuffer->commit()); |
| 737 | } |
| 738 | |
| 739 | } // anonymous namespace |
| 740 | |
| 741 | int llvm_rc_main(int Argc, char **Argv, const llvm::ToolContext &) { |
| 742 | ExitOnErr.setBanner("llvm-rc: " ); |
| 743 | |
| 744 | char **DashDash = std::find_if(first: Argv + 1, last: Argv + Argc, |
| 745 | pred: [](StringRef Str) { return Str == "--" ; }); |
| 746 | ArrayRef<const char *> ArgsArr = ArrayRef(Argv + 1, DashDash); |
| 747 | ArrayRef<const char *> FileArgsArr; |
| 748 | if (DashDash != Argv + Argc) |
| 749 | FileArgsArr = ArrayRef(DashDash + 1, Argv + Argc); |
| 750 | |
| 751 | RcOptions Opts = getOptions(Argv0: Argv[0], ArgsArr, InputArgs: FileArgsArr); |
| 752 | |
| 753 | std::string ResFile = Opts.OutputFile; |
| 754 | if (Opts.InputFormat == Rc) { |
| 755 | if (Opts.OutputFormat == Coff) { |
| 756 | ResFile = createTempFile(Prefix: "rc" , Suffix: "res" ); |
| 757 | TempResFile.setFile(filename: ResFile); |
| 758 | } |
| 759 | doRc(Src: Opts.InputFile, Dest: ResFile, Opts, Argv0: Argv[0]); |
| 760 | } else { |
| 761 | ResFile = Opts.InputFile; |
| 762 | } |
| 763 | if (Opts.OutputFormat == Coff) { |
| 764 | doCvtres(Src: ResFile, Dest: Opts.OutputFile, TargetTriple: Opts.Triple); |
| 765 | } |
| 766 | |
| 767 | return 0; |
| 768 | } |
| 769 | |