mstorsjo updated this revision to Diff 338978.
mstorsjo added a comment.
Herald added a project: clang.
Herald added a subscriber: cfe-commits.

Rebased on top of the updated preceding patch. Adjusted the logic for picking th
e default triple, to preserve the exact spelling of the default triple, if 
isWindowsGNUEnvironment() is true. Added a preprocessing test under clang.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D100756/new/

https://reviews.llvm.org/D100756

Files:
  clang/test/CMakeLists.txt
  clang/test/Preprocessor/Inputs/llvm-windres.h
  clang/test/Preprocessor/llvm-windres.rc
  llvm/test/CMakeLists.txt
  llvm/test/lit.cfg.py
  llvm/test/tools/llvm-rc/codepage.test
  llvm/test/tools/llvm-rc/language.test
  llvm/test/tools/llvm-rc/windres-format.test
  llvm/test/tools/llvm-rc/windres-prefix.test
  llvm/test/tools/llvm-rc/windres-preproc.test
  llvm/test/tools/llvm-rc/windres-target.test
  llvm/test/tools/llvm-rc/windres-version.test
  llvm/tools/llvm-rc/CMakeLists.txt
  llvm/tools/llvm-rc/WindresOpts.td
  llvm/tools/llvm-rc/llvm-rc.cpp

Index: llvm/tools/llvm-rc/llvm-rc.cpp
===================================================================
--- llvm/tools/llvm-rc/llvm-rc.cpp
+++ llvm/tools/llvm-rc/llvm-rc.cpp
@@ -18,8 +18,10 @@
 #include "ResourceScriptToken.h"
 
 #include "llvm/ADT/Triple.h"
+#include "llvm/Object/WindowsResource.h"
 #include "llvm/Option/Arg.h"
 #include "llvm/Option/ArgList.h"
+#include "llvm/Support/CommandLine.h"
 #include "llvm/Support/Error.h"
 #include "llvm/Support/FileSystem.h"
 #include "llvm/Support/FileUtilities.h"
@@ -75,8 +77,39 @@
   RcOptTable() : OptTable(InfoTable, /* IgnoreCase = */ true) {}
 };
 
+enum Windres_ID {
+  WINDRES_INVALID = 0, // This is not a correct option ID.
+#define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM,  \
+               HELPTEXT, METAVAR, VALUES)                                      \
+  WINDRES_##ID,
+#include "WindresOpts.inc"
+#undef OPTION
+};
+
+#define PREFIX(NAME, VALUE) const char *const WINDRES_##NAME[] = VALUE;
+#include "WindresOpts.inc"
+#undef PREFIX
+
+static const opt::OptTable::Info WindresInfoTable[] = {
+#define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM,  \
+               HELPTEXT, METAVAR, VALUES)                                      \
+  {                                                                            \
+      WINDRES_##PREFIX, NAME,         HELPTEXT,                                \
+      METAVAR,          WINDRES_##ID, opt::Option::KIND##Class,                \
+      PARAM,            FLAGS,        WINDRES_##GROUP,                         \
+      WINDRES_##ALIAS,  ALIASARGS,    VALUES},
+#include "WindresOpts.inc"
+#undef OPTION
+};
+
+class WindresOptTable : public opt::OptTable {
+public:
+  WindresOptTable() : OptTable(WindresInfoTable, /* IgnoreCase = */ false) {}
+};
+
 static ExitOnError ExitOnErr;
 static FileRemover TempPreprocFile;
+static FileRemover TempResFile;
 
 LLVM_ATTRIBUTE_NORETURN static void fatalError(const Twine &Message) {
   errs() << Message << "\n";
@@ -121,10 +154,43 @@
   return T.str();
 }
 
-bool preprocess(StringRef Src, StringRef Dst, opt::InputArgList &InputArgs,
+std::string getMingwTriple() {
+  Triple T(sys::getDefaultTargetTriple());
+  if (T.isWindowsGNUEnvironment())
+    return T.str();
+  // Write out the literal form of the vendor/env here, instead of
+  // constructing them with enum values (which end up with them in
+  // normalized form). The literal form of the triple can matter for
+  // finding include files.
+  return (Twine(T.getArchName()) + "-w64-mingw32").str();
+}
+
+enum Format { Rc, Res, Coff, Unknown };
+
+struct RcOptions {
+  bool Preprocess = true;
+  bool PrintCmdAndExit = false;
+  std::string Triple;
+  std::vector<std::string> PreprocessCmd;
+  std::vector<std::string> PreprocessArgs;
+
+  std::string InputFile;
+  Format InputFormat = Rc;
+  std::string OutputFile;
+  Format OutputFormat = Res;
+
+  bool BeVerbose = false;
+  WriterParams Params;
+  bool AppendNull = false;
+  bool IsDryRun = false;
+  // Set the default language; choose en-US arbitrarily.
+  unsigned LangId = (/*PrimaryLangId*/ 0x09) | (/*SubLangId*/ 0x01 << 10);
+};
+
+bool preprocess(StringRef Src, StringRef Dst, const RcOptions &Opts,
                 const char *Argv0) {
   std::string Clang;
-  if (InputArgs.hasArg(OPT__HASH_HASH_HASH)) {
+  if (Opts.PrintCmdAndExit) {
     Clang = "clang";
   } else {
     ErrorOr<std::string> ClangOrErr = findClang(Argv0);
@@ -139,40 +205,27 @@
       return false;
     }
   }
-  std::string PreprocTriple = getClangClTriple();
 
   SmallVector<StringRef, 8> Args = {
-      Clang, "--driver-mode=gcc", "-target", PreprocTriple, "-E",
-      "-xc", "-DRC_INVOKED",      Src,       "-o",          Dst};
-  if (InputArgs.hasArg(OPT_noinclude)) {
-#ifdef _WIN32
-    ::_putenv("INCLUDE=");
-#else
-    ::unsetenv("INCLUDE");
-#endif
-  }
-  for (const auto *Arg :
-       InputArgs.filtered(OPT_includepath, OPT_define, OPT_undef)) {
-    switch (Arg->getOption().getID()) {
-    case OPT_includepath:
-      Args.push_back("-I");
-      break;
-    case OPT_define:
-      Args.push_back("-D");
-      break;
-    case OPT_undef:
-      Args.push_back("-U");
-      break;
-    }
-    Args.push_back(Arg->getValue());
+      Clang, "--driver-mode=gcc", "-target", Opts.Triple, "-E",
+      "-xc", "-DRC_INVOKED"};
+  if (!Opts.PreprocessCmd.empty()) {
+    Args.clear();
+    for (const auto &S : Opts.PreprocessCmd)
+      Args.push_back(S);
   }
-  if (InputArgs.hasArg(OPT__HASH_HASH_HASH) || InputArgs.hasArg(OPT_verbose)) {
+  Args.push_back(Src);
+  Args.push_back("-o");
+  Args.push_back(Dst);
+  for (const auto &S : Opts.PreprocessArgs)
+    Args.push_back(S);
+  if (Opts.PrintCmdAndExit || Opts.BeVerbose) {
     for (const auto &A : Args) {
       outs() << " ";
-      sys::printArg(outs(), A, InputArgs.hasArg(OPT__HASH_HASH_HASH));
+      sys::printArg(outs(), A, Opts.PrintCmdAndExit);
     }
     outs() << "\n";
-    if (InputArgs.hasArg(OPT__HASH_HASH_HASH))
+    if (Opts.PrintCmdAndExit)
       exit(0);
   }
   // The llvm Support classes don't handle reading from stdout of a child
@@ -184,40 +237,329 @@
   return true;
 }
 
-} // anonymous namespace
+static bool consume_back_lower(StringRef &S, const char *str) {
+  if (!S.endswith_lower(str))
+    return false;
+  S = S.drop_back(strlen(str));
+  return true;
+}
 
-int main(int Argc, const char **Argv) {
-  InitLLVM X(Argc, Argv);
-  ExitOnErr.setBanner("llvm-rc: ");
+static std::pair<bool, std::string> isWindres(llvm::StringRef Argv0) {
+  StringRef ProgName = llvm::sys::path::stem(Argv0);
+  // x86_64-w64-mingw32-windres -> x86_64-w64-mingw32, windres
+  // llvm-rc -> "", llvm-rc
+  // aarch64-w64-mingw32-llvm-windres-10.exe -> aarch64-w64-mingw32, llvm-windres
+  ProgName = ProgName.rtrim("0123456789.-");
+  if (!consume_back_lower(ProgName, "windres"))
+    return std::make_pair<bool, std::string>(false, "");
+  consume_back_lower(ProgName, "llvm-");
+  consume_back_lower(ProgName, "-");
+  return std::make_pair<bool, std::string>(true, ProgName.str());
+}
 
-  RcOptTable T;
+Format parseFormat(StringRef S) {
+  Format F = StringSwitch<Format>(S.lower())
+                 .Case("rc", Rc)
+                 .Case("res", Res)
+                 .Case("coff", Coff)
+                 .Default(Unknown);
+  if (F == Unknown)
+    fatalError("Unable to parse '" + Twine(S) + "' as a format");
+  return F;
+}
+
+void deduceFormat(Format &Dest, StringRef File) {
+  Format F = StringSwitch<Format>(sys::path::extension(File.lower()))
+                 .Case(".rc", Rc)
+                 .Case(".res", Res)
+                 .Case(".o", Coff)
+                 .Default(Unknown);
+  if (F != Unknown)
+    Dest = F;
+}
+
+// Preprocessor options passed to GNU windres are passed pretty much as-is
+// to popen(), thus they need to be unescaped like a shell would.
+// (GNU windres backslash escapes spaces in the passed in strings, but
+// nothing else.) Technically, how the shell unescapes things depends on
+// the platform; this unescapes the unix way, which happens to work for
+// a case like -DSTRING=\\\"1.2.3\\\" when running on windows too.
+std::string unescape(StringRef S) {
+  std::string Out;
+  Out.reserve(S.size());
+  for (int I = 0, E = S.size(); I < E; I++) {
+    if (S[I] == '\\') {
+      if (I + 1 < E)
+        Out.push_back(S[++I]);
+      else
+        fatalError("Unterminated escape");
+      continue;
+    }
+    Out.push_back(S[I]);
+  }
+  return Out;
+}
+
+std::vector<std::string> unescapeSplit(StringRef S) {
+  std::vector<std::string> OutArgs;
+  std::string Out;
+  bool InQuote = false;
+  for (int I = 0, E = S.size(); I < E; I++) {
+    if (S[I] == '\\') {
+      if (I + 1 < E)
+        Out.push_back(S[++I]);
+      else
+        fatalError("Unterminated escape");
+      continue;
+    }
+    if (S[I] == '"') {
+      InQuote = !InQuote;
+      continue;
+    }
+    if (S[I] == ' ' && !InQuote) {
+      OutArgs.push_back(Out);
+      Out.clear();
+      continue;
+    }
+    Out.push_back(S[I]);
+  }
+  if (InQuote)
+    fatalError("Unterminated quote");
+  if (!Out.empty())
+    OutArgs.push_back(Out);
+  return OutArgs;
+}
+
+RcOptions parseWindresOptions(ArrayRef<const char *> ArgsArr,
+                              ArrayRef<const char *> InputArgsArray,
+                              std::string Prefix) {
+  WindresOptTable T;
+  RcOptions Opts;
   unsigned MAI, MAC;
-  const char **DashDash = std::find_if(
-      Argv + 1, Argv + Argc, [](StringRef Str) { return Str == "--"; });
-  ArrayRef<const char *> ArgsArr = makeArrayRef(Argv + 1, DashDash);
+  opt::InputArgList InputArgs = T.ParseArgs(ArgsArr, MAI, MAC);
+
+  // The tool prints nothing when invoked with no command-line arguments.
+  if (InputArgs.hasArg(WINDRES_help)) {
+    T.PrintHelp(outs(), "windres [options] file...",
+                "LLVM windres (GNU windres compatible)", false, true);
+    exit(0);
+  }
+
+  if (InputArgs.hasArg(WINDRES_version)) {
+    outs() << "llvm-windres, compatible with GNU windres\n";
+    cl::PrintVersionMessage();
+    exit(0);
+  }
+
+  std::vector<std::string> FileArgs = InputArgs.getAllArgValues(WINDRES_INPUT);
+  FileArgs.insert(FileArgs.end(), InputArgsArray.begin(), InputArgsArray.end());
+
+  if (InputArgs.hasArg(WINDRES_input)) {
+    Opts.InputFile = InputArgs.getLastArgValue(WINDRES_input).str();
+  } else if (!FileArgs.empty()) {
+    Opts.InputFile = FileArgs.front();
+    FileArgs.erase(FileArgs.begin());
+  } else {
+    // TODO: GNU windres takes input on stdin in this case.
+    fatalError("Missing input file");
+  }
+
+  if (InputArgs.hasArg(WINDRES_output)) {
+    Opts.OutputFile = InputArgs.getLastArgValue(WINDRES_output).str();
+  } else if (!FileArgs.empty()) {
+    Opts.OutputFile = FileArgs.front();
+    FileArgs.erase(FileArgs.begin());
+  } else {
+    // TODO: GNU windres writes output in rc form to stdout in this case.
+    fatalError("Missing output file");
+  }
+
+  if (InputArgs.hasArg(WINDRES_input_format)) {
+    Opts.InputFormat =
+        parseFormat(InputArgs.getLastArgValue(WINDRES_input_format));
+  } else {
+    deduceFormat(Opts.InputFormat, Opts.InputFile);
+  }
+  if (Opts.InputFormat == Coff)
+    fatalError("Unsupported input format");
 
+  if (InputArgs.hasArg(WINDRES_output_format)) {
+    Opts.OutputFormat =
+        parseFormat(InputArgs.getLastArgValue(WINDRES_output_format));
+  } else {
+    // The default in windres differs from the default in RcOptions
+    Opts.OutputFormat = Coff;
+    deduceFormat(Opts.OutputFormat, Opts.OutputFile);
+  }
+  if (Opts.OutputFormat == Rc)
+    fatalError("Unsupported output format");
+  if (Opts.InputFormat == Opts.OutputFormat) {
+    outs() << "Nothing to do.\n";
+    exit(0);
+  }
+
+  Opts.PrintCmdAndExit = InputArgs.hasArg(WINDRES__HASH_HASH_HASH);
+  Opts.Preprocess = !InputArgs.hasArg(WINDRES_no_preprocess);
+  Triple TT(Prefix);
+  if (InputArgs.hasArg(WINDRES_target)) {
+    StringRef Value = InputArgs.getLastArgValue(WINDRES_target);
+    if (Value == "pe-i386")
+      Opts.Triple = "i686-w64-mingw32";
+    else if (Value == "pe-x86-64")
+      Opts.Triple = "x86_64-w64-mingw32";
+    else
+      // Implicit extension; if the --target value isn't one of the known
+      // BFD targets, allow setting the full triple string via this instead.
+      Opts.Triple = Value.str();
+  } else if (TT.getArch() != Triple::UnknownArch)
+    Opts.Triple = Prefix;
+  else
+    Opts.Triple = getMingwTriple();
+
+  for (const auto *Arg :
+       InputArgs.filtered(WINDRES_include_dir, WINDRES_define, WINDRES_undef,
+                          WINDRES_preprocessor_arg)) {
+    std::string Unescaped = unescape(Arg->getValue());
+    switch (Arg->getOption().getID()) {
+    case WINDRES_include_dir:
+      Opts.Params.Include.push_back(Unescaped);
+      Opts.PreprocessArgs.push_back("-I");
+      Opts.PreprocessArgs.push_back(Unescaped);
+      break;
+    case WINDRES_define:
+      Opts.PreprocessArgs.push_back("-D");
+      Opts.PreprocessArgs.push_back(Unescaped);
+      break;
+    case WINDRES_undef:
+      Opts.PreprocessArgs.push_back("-U");
+      Opts.PreprocessArgs.push_back(Unescaped);
+      break;
+    case WINDRES_preprocessor_arg:
+      Opts.PreprocessArgs.push_back(Unescaped);
+      break;
+    }
+  }
+  if (InputArgs.hasArg(WINDRES_preprocessor))
+    Opts.PreprocessCmd =
+        unescapeSplit(InputArgs.getLastArgValue(WINDRES_preprocessor));
+
+  Opts.Params.CodePage = CpWin1252; // Different default
+  if (InputArgs.hasArg(WINDRES_codepage)) {
+    if (InputArgs.getLastArgValue(WINDRES_codepage)
+            .getAsInteger(10, Opts.Params.CodePage))
+      fatalError("Invalid code page: " +
+                 InputArgs.getLastArgValue(WINDRES_codepage));
+  }
+  if (InputArgs.hasArg(WINDRES_language)) {
+    if (InputArgs.getLastArgValue(WINDRES_language)
+            .getAsInteger(16, Opts.LangId))
+      fatalError("Invalid language id: " +
+                 InputArgs.getLastArgValue(WINDRES_language));
+  }
+
+  Opts.BeVerbose = InputArgs.hasArg(WINDRES_verbose);
+
+  return Opts;
+}
+
+RcOptions parseRcOptions(ArrayRef<const char *> ArgsArr,
+                         ArrayRef<const char *> InputArgsArray) {
+  RcOptTable T;
+  RcOptions Opts;
+  unsigned MAI, MAC;
   opt::InputArgList InputArgs = T.ParseArgs(ArgsArr, MAI, MAC);
 
   // The tool prints nothing when invoked with no command-line arguments.
   if (InputArgs.hasArg(OPT_help)) {
     T.PrintHelp(outs(), "rc [options] file...", "Resource Converter", false);
-    return 0;
+    exit(0);
   }
 
-  const bool BeVerbose = InputArgs.hasArg(OPT_verbose);
-
   std::vector<std::string> InArgsInfo = InputArgs.getAllArgValues(OPT_INPUT);
-  if (DashDash != Argv + Argc)
-    InArgsInfo.insert(InArgsInfo.end(), DashDash + 1, Argv + Argc);
+  InArgsInfo.insert(InArgsInfo.end(), InputArgsArray.begin(),
+                    InputArgsArray.end());
   if (InArgsInfo.size() != 1) {
     fatalError("Exactly one input file should be provided.");
   }
 
-  std::string PreprocessedFile = InArgsInfo[0];
-  if (!InputArgs.hasArg(OPT_no_preprocess)) {
+  Opts.PrintCmdAndExit = InputArgs.hasArg(OPT__HASH_HASH_HASH);
+  Opts.Triple = getClangClTriple();
+  for (const auto *Arg :
+       InputArgs.filtered(OPT_includepath, OPT_define, OPT_undef)) {
+    switch (Arg->getOption().getID()) {
+    case OPT_includepath:
+      Opts.PreprocessArgs.push_back("-I");
+      break;
+    case OPT_define:
+      Opts.PreprocessArgs.push_back("-D");
+      break;
+    case OPT_undef:
+      Opts.PreprocessArgs.push_back("-U");
+      break;
+    }
+    Opts.PreprocessArgs.push_back(Arg->getValue());
+  }
+
+  Opts.InputFile = InArgsInfo[0];
+  Opts.BeVerbose = InputArgs.hasArg(OPT_verbose);
+  Opts.Preprocess = !InputArgs.hasArg(OPT_no_preprocess);
+  Opts.Params.Include = InputArgs.getAllArgValues(OPT_includepath);
+  Opts.Params.NoInclude = InputArgs.hasArg(OPT_noinclude);
+  if (Opts.Params.NoInclude) {
+    // Clear the INLCUDE variable for the external preprocessor
+#ifdef _WIN32
+    ::_putenv("INCLUDE=");
+#else
+    ::unsetenv("INCLUDE");
+#endif
+  }
+  if (InputArgs.hasArg(OPT_codepage)) {
+    if (InputArgs.getLastArgValue(OPT_codepage)
+            .getAsInteger(10, Opts.Params.CodePage))
+      fatalError("Invalid code page: " +
+                 InputArgs.getLastArgValue(OPT_codepage));
+  }
+  Opts.IsDryRun = InputArgs.hasArg(OPT_dry_run);
+  auto OutArgsInfo = InputArgs.getAllArgValues(OPT_fileout);
+  if (OutArgsInfo.empty()) {
+    SmallString<128> OutputFile(Opts.InputFile);
+    llvm::sys::fs::make_absolute(OutputFile);
+    llvm::sys::path::replace_extension(OutputFile, "res");
+    OutArgsInfo.push_back(std::string(OutputFile.str()));
+  }
+  if (!Opts.IsDryRun) {
+    if (OutArgsInfo.size() != 1)
+      fatalError(
+          "No more than one output file should be provided (using /FO flag).");
+    Opts.OutputFile = OutArgsInfo[0];
+  }
+  Opts.AppendNull = InputArgs.hasArg(OPT_add_null);
+  if (InputArgs.hasArg(OPT_lang_id)) {
+    if (InputArgs.getLastArgValue(OPT_lang_id).getAsInteger(16, Opts.LangId))
+      fatalError("Invalid language id: " +
+                 InputArgs.getLastArgValue(OPT_lang_id));
+  }
+  return Opts;
+}
+
+RcOptions getOptions(const char *Argv0, ArrayRef<const char *> ArgsArr,
+                     ArrayRef<const char *> InputArgs) {
+  std::string Prefix;
+  bool IsWindres;
+  std::tie(IsWindres, Prefix) = isWindres(Argv0);
+  if (IsWindres)
+    return parseWindresOptions(ArgsArr, InputArgs, Prefix);
+  else
+    return parseRcOptions(ArgsArr, InputArgs);
+}
+
+void doRc(std::string Src, std::string Dest, RcOptions &Opts,
+          const char *Argv0) {
+  std::string PreprocessedFile = Src;
+  if (Opts.Preprocess) {
     std::string OutFile = createTempFile("preproc", "rc");
     TempPreprocFile.setFile(OutFile);
-    if (preprocess(InArgsInfo[0], OutFile, InputArgs, Argv[0]))
+    if (preprocess(Src, OutFile, Opts, Argv0))
       PreprocessedFile = OutFile;
   }
 
@@ -225,7 +567,7 @@
   ErrorOr<std::unique_ptr<MemoryBuffer>> File =
       MemoryBuffer::getFile(PreprocessedFile);
   if (!File) {
-    fatalError("Error opening file '" + Twine(InArgsInfo[0]) +
+    fatalError("Error opening file '" + Twine(PreprocessedFile) +
                "': " + File.getError().message());
   }
 
@@ -235,7 +577,7 @@
   std::string FilteredContents = filterCppOutput(Contents);
   std::vector<RCToken> Tokens = ExitOnErr(tokenizeRC(FilteredContents));
 
-  if (BeVerbose) {
+  if (Opts.BeVerbose) {
     const Twine TokenNames[] = {
 #define TOKEN(Name) #Name,
 #define SHORT_TOKEN(Name, Ch) #Name,
@@ -252,80 +594,129 @@
     }
   }
 
-  WriterParams Params;
-  SmallString<128> InputFile(InArgsInfo[0]);
+  WriterParams &Params = Opts.Params;
+  SmallString<128> InputFile(Src);
   llvm::sys::fs::make_absolute(InputFile);
   Params.InputFilePath = InputFile;
-  Params.Include = InputArgs.getAllArgValues(OPT_includepath);
-  Params.NoInclude = InputArgs.hasArg(OPT_noinclude);
 
-  if (InputArgs.hasArg(OPT_codepage)) {
-    if (InputArgs.getLastArgValue(OPT_codepage)
-            .getAsInteger(10, Params.CodePage))
-      fatalError("Invalid code page: " +
-                 InputArgs.getLastArgValue(OPT_codepage));
-    switch (Params.CodePage) {
-    case CpAcp:
-    case CpWin1252:
-    case CpUtf8:
-      break;
-    default:
-      fatalError(
-          "Unsupported code page, only 0, 1252 and 65001 are supported!");
-    }
+  switch (Params.CodePage) {
+  case CpAcp:
+  case CpWin1252:
+  case CpUtf8:
+    break;
+  default:
+    fatalError("Unsupported code page, only 0, 1252 and 65001 are supported!");
   }
 
   std::unique_ptr<ResourceFileWriter> Visitor;
-  bool IsDryRun = InputArgs.hasArg(OPT_dry_run);
-
-  if (!IsDryRun) {
-    auto OutArgsInfo = InputArgs.getAllArgValues(OPT_fileout);
-    if (OutArgsInfo.empty()) {
-      SmallString<128> OutputFile = InputFile;
-      llvm::sys::path::replace_extension(OutputFile, "res");
-      OutArgsInfo.push_back(std::string(OutputFile.str()));
-    }
-
-    if (OutArgsInfo.size() != 1)
-      fatalError(
-          "No more than one output file should be provided (using /FO flag).");
 
+  if (!Opts.IsDryRun) {
     std::error_code EC;
     auto FOut = std::make_unique<raw_fd_ostream>(
-        OutArgsInfo[0], EC, sys::fs::FA_Read | sys::fs::FA_Write);
+        Dest, EC, sys::fs::FA_Read | sys::fs::FA_Write);
     if (EC)
-      fatalError("Error opening output file '" + OutArgsInfo[0] +
-                 "': " + EC.message());
+      fatalError("Error opening output file '" + Dest + "': " + EC.message());
     Visitor = std::make_unique<ResourceFileWriter>(Params, std::move(FOut));
-    Visitor->AppendNull = InputArgs.hasArg(OPT_add_null);
+    Visitor->AppendNull = Opts.AppendNull;
 
     ExitOnErr(NullResource().visit(Visitor.get()));
 
-    // Set the default language; choose en-US arbitrarily.
-    unsigned PrimaryLangId = 0x09, SubLangId = 0x01;
-    if (InputArgs.hasArg(OPT_lang_id)) {
-      unsigned LangId;
-      if (InputArgs.getLastArgValue(OPT_lang_id).getAsInteger(16, LangId))
-        fatalError("Invalid language id: " +
-                   InputArgs.getLastArgValue(OPT_lang_id));
-      PrimaryLangId = LangId & 0x3ff;
-      SubLangId = LangId >> 10;
-    }
+    unsigned PrimaryLangId = Opts.LangId & 0x3ff;
+    unsigned SubLangId = Opts.LangId >> 10;
     ExitOnErr(LanguageResource(PrimaryLangId, SubLangId).visit(Visitor.get()));
   }
 
   rc::RCParser Parser{std::move(Tokens)};
   while (!Parser.isEof()) {
     auto Resource = ExitOnErr(Parser.parseSingleResource());
-    if (BeVerbose)
+    if (Opts.BeVerbose)
       Resource->log(outs());
-    if (!IsDryRun)
+    if (!Opts.IsDryRun)
       ExitOnErr(Resource->visit(Visitor.get()));
   }
 
   // STRINGTABLE resources come at the very end.
-  if (!IsDryRun)
+  if (!Opts.IsDryRun)
     ExitOnErr(Visitor->dumpAllStringTables());
+}
+
+void doCvtres(std::string Src, std::string Dest, std::string TargetTriple) {
+  object::WindowsResourceParser Parser;
+
+  ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr =
+      MemoryBuffer::getFile(Src);
+  if (!BufferOrErr)
+    fatalError("Error opening file '" + Twine(Src) +
+               "': " + BufferOrErr.getError().message());
+  std::unique_ptr<MemoryBuffer> &Buffer = BufferOrErr.get();
+  std::unique_ptr<object::WindowsResource> Binary =
+      ExitOnErr(object::WindowsResource::createWindowsResource(
+          Buffer->getMemBufferRef()));
+
+  std::vector<std::string> Duplicates;
+  ExitOnErr(Parser.parse(Binary.get(), Duplicates));
+  for (const auto &DupeDiag : Duplicates)
+    fatalError("Duplicate resources: " + DupeDiag);
+
+  Triple T(TargetTriple);
+  COFF::MachineTypes MachineType;
+  switch (T.getArch()) {
+  case Triple::x86:
+    MachineType = COFF::IMAGE_FILE_MACHINE_I386;
+    break;
+  case Triple::x86_64:
+    MachineType = COFF::IMAGE_FILE_MACHINE_AMD64;
+    break;
+  case Triple::arm:
+  case Triple::thumb:
+    MachineType = COFF::IMAGE_FILE_MACHINE_ARMNT;
+    break;
+  case Triple::aarch64:
+    MachineType = COFF::IMAGE_FILE_MACHINE_ARM64;
+    break;
+  default:
+    fatalError("Unsupported architecture in target '" + Twine(TargetTriple) +
+               "'");
+  }
+
+  std::unique_ptr<MemoryBuffer> OutputBuffer =
+      ExitOnErr(object::writeWindowsResourceCOFF(MachineType, Parser,
+                                                 /*DateTimeStamp*/ 0));
+  std::unique_ptr<FileOutputBuffer> FileBuffer =
+      ExitOnErr(FileOutputBuffer::create(Dest, OutputBuffer->getBufferSize()));
+  std::copy(OutputBuffer->getBufferStart(), OutputBuffer->getBufferEnd(),
+            FileBuffer->getBufferStart());
+  ExitOnErr(FileBuffer->commit());
+}
+
+} // anonymous namespace
+
+int main(int Argc, const char **Argv) {
+  InitLLVM X(Argc, Argv);
+  ExitOnErr.setBanner("llvm-rc: ");
+
+  const char **DashDash = std::find_if(
+      Argv + 1, Argv + Argc, [](StringRef Str) { return Str == "--"; });
+  ArrayRef<const char *> ArgsArr = makeArrayRef(Argv + 1, DashDash);
+  ArrayRef<const char *> FileArgsArr;
+  if (DashDash != Argv + Argc)
+    FileArgsArr = makeArrayRef(DashDash + 1, Argv + Argc);
+
+  RcOptions Opts = getOptions(Argv[0], ArgsArr, FileArgsArr);
+
+  std::string ResFile = Opts.OutputFile;
+  if (Opts.InputFormat == Rc) {
+    if (Opts.OutputFormat == Coff) {
+      ResFile = createTempFile("rc", "res");
+      TempResFile.setFile(ResFile);
+    }
+    doRc(Opts.InputFile, ResFile, Opts, Argv[0]);
+  } else {
+    ResFile = Opts.InputFile;
+  }
+  if (Opts.OutputFormat == Coff) {
+    doCvtres(ResFile, Opts.OutputFile, Opts.Triple);
+  }
 
   return 0;
 }
Index: llvm/tools/llvm-rc/WindresOpts.td
===================================================================
--- /dev/null
+++ llvm/tools/llvm-rc/WindresOpts.td
@@ -0,0 +1,62 @@
+include "llvm/Option/OptParser.td"
+
+multiclass Long<string name, string help> {
+  def NAME: Separate<["--"], name>;
+  def NAME # _eq: Joined<["--"], name # "=">, Alias<!cast<Separate>(NAME)>,
+    HelpText<help>;
+}
+
+multiclass LongAlias<string name, Option orig> {
+  def NAME: Separate<["--"], name>, Alias<orig>;
+  def NAME # _eq: Joined<["--"], name # "=">, Alias<orig>;
+}
+
+multiclass LongShort<string short, string long, string help> {
+  def NAME: Separate<["--"], long>;
+  def NAME # _eq: Joined<["--"], long # "=">, Alias<!cast<Separate>(NAME)>,
+    HelpText<help>;
+  def NAME # _short: JoinedOrSeparate<["-"], short>, Alias<!cast<Separate>(NAME)>;
+}
+
+multiclass F<string short, string long, string help> {
+  def NAME: Flag<["-"], short>;
+  def NAME # _long: Flag<["--"], long>, Alias<!cast<Flag>(NAME)>,
+    HelpText<help>;
+}
+
+defm input : LongShort<"i", "input", "Input file">;
+
+defm output : LongShort<"o", "output", "Output file">;
+
+defm input_format : LongShort<"J", "input-format", "Input format">;
+
+defm output_format : LongShort<"O", "output-format", "Output format">;
+
+defm preprocessor : Long<"preprocessor", "Custom preprocessor command">;
+defm preprocessor_arg : Long<"preprocessor-arg", "Preprocessor command argument">;
+
+defm target : LongShort<"F", "target", "Target BFD format name">;
+
+defm include_dir : LongShort<"I", "include-dir", "Include directory">;
+defm include_alias : LongAlias<"include", include_dir>;
+
+defm define : LongShort<"D", "define", "Define to pass to the preprocessor">;
+
+defm undef : LongShort<"U", "undefine", "Undefine to pass to the preprocessor">;
+
+defm codepage : LongShort<"c", "codepage", "Default codepage to use">;
+
+defm language : LongShort<"l", "language", "Default language to use (0x0-0xffff)">;
+
+defm verbose : F<"v", "verbose", "Enable verbose output">;
+defm version : F<"V", "version", "Display version">;
+
+defm help : F<"h", "help", "Display this message and exit">;
+
+// Print (but do not run) the commands to run for preprocessing
+def _HASH_HASH_HASH : Flag<["-"], "###">;
+
+def no_preprocess : Flag<["--"], "no-preprocess">;
+
+// Unimplemented options for compatibility
+def use_temp_file: Flag<["--"], "use-temp-file">;
Index: llvm/tools/llvm-rc/CMakeLists.txt
===================================================================
--- llvm/tools/llvm-rc/CMakeLists.txt
+++ llvm/tools/llvm-rc/CMakeLists.txt
@@ -1,12 +1,16 @@
 set(LLVM_LINK_COMPONENTS
+  Object
   Option
   Support
   )
 
 set(LLVM_TARGET_DEFINITIONS Opts.td)
-
 tablegen(LLVM Opts.inc -gen-opt-parser-defs)
-add_public_tablegen_target(RcTableGen)
+add_public_tablegen_target(RcOptsTableGen)
+
+set(LLVM_TARGET_DEFINITIONS WindresOpts.td)
+tablegen(LLVM WindresOpts.inc -gen-opt-parser-defs)
+add_public_tablegen_target(WindresOptsTableGen)
 
 add_llvm_tool(llvm-rc
   llvm-rc.cpp
@@ -16,3 +20,9 @@
   ResourceScriptStmt.cpp
   ResourceScriptToken.cpp
   )
+
+add_llvm_tool_symlink(llvm-windres llvm-rc)
+
+if(LLVM_INSTALL_BINUTILS_SYMLINKS)
+  add_llvm_tool_symlink(windres llvm-rc)
+endif()
Index: llvm/test/tools/llvm-rc/windres-version.test
===================================================================
--- /dev/null
+++ llvm/test/tools/llvm-rc/windres-version.test
@@ -0,0 +1,6 @@
+; RUN: llvm-windres --version | FileCheck %s
+
+; Check that the printed version string contains the words "GNU windres",
+; which some build systems look for.
+
+; CHECK: GNU windres
Index: llvm/test/tools/llvm-rc/windres-target.test
===================================================================
--- /dev/null
+++ llvm/test/tools/llvm-rc/windres-target.test
@@ -0,0 +1,34 @@
+; Check handling of the -F/--target option for setting a specific BFD
+; target name.
+
+; RUN: llvm-windres -F pe-i386 -### %p/Inputs/empty.rc %t.res | FileCheck %s --check-prefix=CHECK-I686
+; RUN: llvm-windres -Fpe-i386 -### %p/Inputs/empty.rc %t.res | FileCheck %s --check-prefix=CHECK-I686
+; CHECK-I686: "clang" "--driver-mode=gcc" "-target" "i686-w64-mingw32"
+; RUN: llvm-windres --target pe-x86-64 -### %p/Inputs/empty.rc %t.res | FileCheck %s --check-prefix=CHECK-X86-64
+; RUN: llvm-windres --target=pe-x86-64 -### %p/Inputs/empty.rc %t.res | FileCheck %s --check-prefix=CHECK-X86-64
+; CHECK-X86-64: "clang" "--driver-mode=gcc" "-target" "x86_64-w64-mingw32"
+
+; LLVM windres specific: Check that we can pass a full triple via the
+; -F/--target option, if it doesn't match the BFD target names.
+
+; RUN: llvm-windres -F armv7-w64-mingw32 -### %p/Inputs/empty.rc %t.res | FileCheck %s --check-prefix=CHECK-ARMV7
+; RUN: llvm-windres --target armv7-w64-mingw32 -### %p/Inputs/empty.rc %t.res | FileCheck %s --check-prefix=CHECK-ARMV7
+; CHECK-ARMV7: "clang" "--driver-mode=gcc" "-target" "armv7-w64-mingw32"
+
+; Check the actual written object types.
+
+; RUN: llvm-windres --no-preprocess -F i686-w64-mingw32 %p/Inputs/tag-stringtable-basic.rc %t.o
+; RUN: llvm-readobj --coff-resources %t.o | FileCheck %s --check-prefixes=CHECK-OBJ,CHECK-OBJ-I686
+; RUN: llvm-windres --no-preprocess -F x86_64-w64-mingw32 %p/Inputs/tag-stringtable-basic.rc %t.o
+; RUN: llvm-readobj --coff-resources %t.o | FileCheck %s --check-prefixes=CHECK-OBJ,CHECK-OBJ-X86-64
+; RUN: llvm-windres --no-preprocess -F armv7-w64-mingw32 %p/Inputs/tag-stringtable-basic.rc %t.o
+; RUN: llvm-readobj --coff-resources %t.o | FileCheck %s --check-prefixes=CHECK-OBJ,CHECK-OBJ-ARMV7
+; RUN: llvm-windres --no-preprocess -F aarch64-w64-mingw32 %p/Inputs/tag-stringtable-basic.rc %t.o
+; RUN: llvm-readobj --coff-resources %t.o | FileCheck %s --check-prefixes=CHECK-OBJ,CHECK-OBJ-AARCH64
+
+; CHECK-OBJ-I686: Format: COFF-i386
+; CHECK-OBJ-X86-64: Format: COFF-x86-64
+; CHECK-OBJ-ARMV7: Format: COFF-ARM{{$}}
+; CHECK-OBJ-AARCH64: Format: COFF-ARM64
+; CHECK-OBJ: Resources [
+; CHECK-OBJ:   Total Number of Resources:
Index: llvm/test/tools/llvm-rc/windres-preproc.test
===================================================================
--- /dev/null
+++ llvm/test/tools/llvm-rc/windres-preproc.test
@@ -0,0 +1,4 @@
+; RUN: llvm-windres -### --include-dir %p/incdir1 --include %p/incdir2 "-DFOO1=\\\"foo bar\\\"" -UFOO2 -D FOO3 --preprocessor-arg "-DFOO4=\\\"baz baz\\\"" %p/Inputs/empty.rc %t.res | FileCheck %s --check-prefix=CHECK1
+; CHECK1: {{^}} "clang" "--driver-mode=gcc" "-target" "{{.*}}-w64-mingw32" "-E" "-xc" "-DRC_INVOKED" "{{.*}}empty.rc" "-o" "{{.*}}preproc-{{.*}}.rc" "-I" "{{.*}}incdir1" "-I" "{{.*}}incdir2" "-D" "FOO1=\"foo bar\"" "-U" "FOO2" "-D" "FOO3" "-DFOO4=\"baz baz\""{{$}}
+; RUN: llvm-windres -### --preprocessor "i686-w64-mingw32-gcc -E -DFOO=\\\"foo\\ bar\\\"" %p/Inputs/empty.rc %t.res | FileCheck %s --check-prefix=CHECK2
+; CHECK2: {{^}} "i686-w64-mingw32-gcc" "-E" "-DFOO=\"foo bar\"" "{{.*}}empty.rc" "-o" "{{.*}}preproc-{{.*}}.rc"{{$}}
Index: llvm/test/tools/llvm-rc/windres-prefix.test
===================================================================
--- /dev/null
+++ llvm/test/tools/llvm-rc/windres-prefix.test
@@ -0,0 +1,18 @@
+; REQUIRES: shell
+
+; RUN: rm -rf %t && mkdir %t
+
+; Check that a triple prefix on the executable gets picked up as target triple.
+
+; RUN: ln -fs $(which llvm-windres) %t/aarch64-w64-mingw32-windres
+; RUN: %t/aarch64-w64-mingw32-windres -### %p/Inputs/empty.rc %t.res | FileCheck %s --check-prefix=CHECK-PREPROC
+; CHECK-PREPROC: "clang" "--driver-mode=gcc" "-target" "aarch64-w64-mingw32"
+
+; Check that the triple prefix also affects the output object file type.
+
+; RUN: %t/aarch64-w64-mingw32-windres --no-preprocess %p/Inputs/tag-stringtable-basic.rc %t.o
+; RUN: llvm-readobj --coff-resources %t.o | FileCheck %s --check-prefix=CHECK-OBJ
+
+; CHECK-OBJ: Format: COFF-ARM64
+; CHECK-OBJ: Resources [
+; CHECK-OBJ:   Total Number of Resources:
Index: llvm/test/tools/llvm-rc/windres-format.test
===================================================================
--- /dev/null
+++ llvm/test/tools/llvm-rc/windres-format.test
@@ -0,0 +1,40 @@
+; Check that the various input/output formats (rc/res/coff) are implied
+; from file suffixes.
+
+; RUN: rm -f %t.res
+; RUN: llvm-windres --no-cpp %p/Inputs/tag-stringtable-basic.rc %t.res
+; RUN: llvm-readobj %t.res | FileCheck %s --check-prefix=CHECK-RES
+
+; RUN: rm -f %t.o
+; RUN: llvm-windres --no-cpp -F pe-x86-64 %p/Inputs/tag-stringtable-basic.rc %t.o
+; RUN: llvm-readobj --coff-resources %t.o | FileCheck %s --check-prefix=CHECK-OBJ
+
+; RUN: rm -f %t.o
+; RUN: llvm-windres -F pe-x86-64 %t.res %t.o
+; RUN: llvm-readobj --coff-resources %t.o | FileCheck %s --check-prefix=CHECK-OBJ
+
+; Check that we can specify the input/output file types explicitly.
+; Also check options for specifying the input/output file names.
+
+; RUN: cat %p/Inputs/tag-stringtable-basic.rc > %t-anonymous
+; RUN: rm -f %t-anonymous2
+; RUN: llvm-windres --no-cpp -O res -J rc -o %t-anonymous2 -i %t-anonymous
+; RUN: llvm-readobj %t-anonymous2 | FileCheck %s --check-prefix=CHECK-RES
+
+; RUN: rm -f %t-anonymous3
+; RUN: llvm-windres -F pe-x86-64 -J res -O coff -i%t-anonymous2 -o%t-anonymous3
+; RUN: llvm-readobj --coff-resources %t-anonymous3 | FileCheck %s --check-prefix=CHECK-OBJ
+
+; CHECK-RES: Resource type (int): STRINGTABLE
+
+; CHECK-OBJ: Format: COFF-x86-64
+; CHECK-OBJ: Resources [
+; CHECK-OBJ:   Total Number of Resources:
+
+; Check for format conversions that currently aren't supported.
+
+; RUN: not llvm-windres -F pe-x86-64 -J res -O rc -i%t-anonymous2 -o%t-anonymous 2>&1 | FileCheck %s --check-prefix=CHECK-ERROR-OUTPUT
+; RUN: not llvm-windres -F pe-x86-64 -J coff -O res -i%t-anonymous3 -o%t-anonymous2 2>&1 | FileCheck %s --check-prefix=CHECK-ERROR-INPUT
+
+; CHECK-ERROR-OUTPUT: Unsupported output format
+; CHECK-ERROR-INPUT: Unsupported input format
Index: llvm/test/tools/llvm-rc/language.test
===================================================================
--- llvm/test/tools/llvm-rc/language.test
+++ llvm/test/tools/llvm-rc/language.test
@@ -2,6 +2,10 @@
 ; RUN: llvm-readobj %t.res | FileCheck %s
 ; RUN: llvm-rc -no-preprocess /l40A /FO %t.res -- %p/Inputs/language.rc
 ; RUN: llvm-readobj %t.res | FileCheck %s
+; RUN: llvm-windres --no-cpp -l 40A %p/Inputs/language.rc %t.res
+; RUN: llvm-readobj %t.res | FileCheck %s
+; RUN: llvm-windres --no-cpp --language 40A %p/Inputs/language.rc %t.res
+; RUN: llvm-readobj %t.res | FileCheck %s
 
 ; CHECK:      Resource name (int): 1
 ; CHECK-NEXT: Data version:
Index: llvm/test/tools/llvm-rc/codepage.test
===================================================================
--- llvm/test/tools/llvm-rc/codepage.test
+++ llvm/test/tools/llvm-rc/codepage.test
@@ -1,5 +1,9 @@
 ; RUN: llvm-rc -no-preprocess /C 65001 /FO %t.utf8.res -- %p/Inputs/utf8.rc
 ; RUN: llvm-readobj %t.utf8.res | FileCheck %s --check-prefix=UTF8
+; RUN: llvm-windres --no-cpp -c 65001 %p/Inputs/utf8.rc %t.utf8.res
+; RUN: llvm-readobj %t.utf8.res | FileCheck %s --check-prefix=UTF8
+; RUN: llvm-windres --no-cpp --codepage 65001 %p/Inputs/utf8.rc %t.utf8.res
+; RUN: llvm-readobj %t.utf8.res | FileCheck %s --check-prefix=UTF8
 
 ; UTF8:      Resource type (int): STRINGTABLE (ID 6)
 ; UTF8-NEXT: Resource name (int): 1
@@ -24,6 +28,8 @@
 
 ; RUN: llvm-rc -no-preprocess /C 1252 /FO %t.cp1252.res -- %p/Inputs/cp1252.rc
 ; RUN: llvm-readobj %t.cp1252.res | FileCheck %s --check-prefix=CP1252
+; RUN: llvm-windres --no-cpp -c 1252 %p/Inputs/cp1252.rc %t.cp1252.res
+; RUN: llvm-readobj %t.cp1252.res | FileCheck %s --check-prefix=CP1252
 
 ; CP1252:      Resource type (int): STRINGTABLE (ID 6)
 ; CP1252-NEXT: Resource name (int): 1
Index: llvm/test/lit.cfg.py
===================================================================
--- llvm/test/lit.cfg.py
+++ llvm/test/lit.cfg.py
@@ -163,7 +163,8 @@
     'llvm-modextract', 'llvm-nm', 'llvm-objcopy', 'llvm-objdump',
     'llvm-pdbutil', 'llvm-profdata', 'llvm-ranlib', 'llvm-rc', 'llvm-readelf',
     'llvm-readobj', 'llvm-rtdyld', 'llvm-size', 'llvm-split', 'llvm-strings',
-    'llvm-strip', 'llvm-tblgen', 'llvm-undname', 'llvm-c-test', 'llvm-cxxfilt',
+    'llvm-strip', 'llvm-tblgen', 'llvm-undname', 'llvm-windres',
+    'llvm-c-test', 'llvm-cxxfilt',
     'llvm-xray', 'yaml2obj', 'obj2yaml', 'yaml-bench', 'verify-uselistorder',
     'bugpoint', 'llc', 'llvm-symbolizer', 'opt', 'sancov', 'sanstats'])
 
Index: llvm/test/CMakeLists.txt
===================================================================
--- llvm/test/CMakeLists.txt
+++ llvm/test/CMakeLists.txt
@@ -116,6 +116,7 @@
           llvm-symbolizer
           llvm-tblgen
           llvm-undname
+          llvm-windres
           llvm-xray
           not
           obj2yaml
Index: clang/test/Preprocessor/llvm-windres.rc
===================================================================
--- /dev/null
+++ clang/test/Preprocessor/llvm-windres.rc
@@ -0,0 +1,8 @@
+// RUN: llvm-windres -I%p/Inputs %s %t.res
+// RUN: llvm-readobj %t.res | FileCheck %s
+// CHECK: Resource type (int): RCDATA (ID 10)
+// CHECK: Resource name (int): 42
+#include "llvm-windres.h"
+MY_ID RCDATA {
+  "a long string of data"
+}
Index: clang/test/Preprocessor/Inputs/llvm-windres.h
===================================================================
--- /dev/null
+++ clang/test/Preprocessor/Inputs/llvm-windres.h
@@ -0,0 +1,10 @@
+#ifndef RC_INVOKED
+#error RC_INVOKED not defined
+#endif
+#ifndef _WIN32
+#error _WIN32 not defined
+#endif
+#ifndef __MINGW32__
+#error __MINGW32__ not defined
+#endif
+#define MY_ID 42
Index: clang/test/CMakeLists.txt
===================================================================
--- clang/test/CMakeLists.txt
+++ clang/test/CMakeLists.txt
@@ -125,6 +125,7 @@
     llvm-readobj
     llvm-strip
     llvm-symbolizer
+    llvm-windres
     opt
     split-file
     yaml2obj
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to