diff --git a/clang/test/CMakeLists.txt b/clang/test/CMakeLists.txt --- a/clang/test/CMakeLists.txt +++ b/clang/test/CMakeLists.txt @@ -125,6 +125,7 @@ llvm-readobj llvm-strip llvm-symbolizer + llvm-windres opt split-file yaml2obj diff --git a/clang/test/Preprocessor/Inputs/llvm-windres.h b/clang/test/Preprocessor/Inputs/llvm-windres.h new file mode 100644 --- /dev/null +++ b/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 diff --git a/clang/test/Preprocessor/llvm-windres.rc b/clang/test/Preprocessor/llvm-windres.rc new file mode 100644 --- /dev/null +++ b/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" +} diff --git a/llvm/test/CMakeLists.txt b/llvm/test/CMakeLists.txt --- a/llvm/test/CMakeLists.txt +++ b/llvm/test/CMakeLists.txt @@ -116,6 +116,7 @@ llvm-symbolizer llvm-tblgen llvm-undname + llvm-windres llvm-xray not obj2yaml diff --git a/llvm/test/lit.cfg.py b/llvm/test/lit.cfg.py --- a/llvm/test/lit.cfg.py +++ b/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']) diff --git a/llvm/test/tools/llvm-rc/codepage.test b/llvm/test/tools/llvm-rc/codepage.test --- a/llvm/test/tools/llvm-rc/codepage.test +++ b/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 diff --git a/llvm/test/tools/llvm-rc/language.test b/llvm/test/tools/llvm-rc/language.test --- a/llvm/test/tools/llvm-rc/language.test +++ b/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: diff --git a/llvm/test/tools/llvm-rc/windres-format.test b/llvm/test/tools/llvm-rc/windres-format.test new file mode 100644 --- /dev/null +++ b/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 diff --git a/llvm/test/tools/llvm-rc/windres-prefix.test b/llvm/test/tools/llvm-rc/windres-prefix.test new file mode 100644 --- /dev/null +++ b/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: diff --git a/llvm/test/tools/llvm-rc/windres-preproc.test b/llvm/test/tools/llvm-rc/windres-preproc.test new file mode 100644 --- /dev/null +++ b/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"{{$}} diff --git a/llvm/test/tools/llvm-rc/windres-target.test b/llvm/test/tools/llvm-rc/windres-target.test new file mode 100644 --- /dev/null +++ b/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: diff --git a/llvm/test/tools/llvm-rc/windres-version.test b/llvm/test/tools/llvm-rc/windres-version.test new file mode 100644 --- /dev/null +++ b/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 diff --git a/llvm/tools/llvm-rc/CMakeLists.txt b/llvm/tools/llvm-rc/CMakeLists.txt --- a/llvm/tools/llvm-rc/CMakeLists.txt +++ b/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() diff --git a/llvm/tools/llvm-rc/WindresOpts.td b/llvm/tools/llvm-rc/WindresOpts.td new file mode 100644 --- /dev/null +++ b/llvm/tools/llvm-rc/WindresOpts.td @@ -0,0 +1,62 @@ +include "llvm/Option/OptParser.td" + +multiclass Long { + def NAME: Separate<["--"], name>; + def NAME # _eq: Joined<["--"], name # "=">, Alias(NAME)>, + HelpText; +} + +multiclass LongAlias { + def NAME: Separate<["--"], name>, Alias; + def NAME # _eq: Joined<["--"], name # "=">, Alias; +} + +multiclass LongShort { + def NAME: Separate<["--"], long>; + def NAME # _eq: Joined<["--"], long # "=">, Alias(NAME)>, + HelpText; + def NAME # _short: JoinedOrSeparate<["-"], short>, Alias(NAME)>; +} + +multiclass F { + def NAME: Flag<["-"], short>; + def NAME # _long: Flag<["--"], long>, Alias(NAME)>, + HelpText; +} + +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">; diff --git a/llvm/tools/llvm-rc/llvm-rc.cpp b/llvm/tools/llvm-rc/llvm-rc.cpp --- a/llvm/tools/llvm-rc/llvm-rc.cpp +++ b/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 PreprocessCmd; + std::vector 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 ClangOrErr = findClang(Argv0); @@ -139,40 +205,27 @@ return false; } } - std::string PreprocTriple = getClangClTriple(); SmallVector 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 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(false, ""); + consume_back_lower(ProgName, "llvm-"); + consume_back_lower(ProgName, "-"); + return std::make_pair(true, ProgName.str()); +} - RcOptTable T; +Format parseFormat(StringRef S) { + Format F = StringSwitch(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(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 unescapeSplit(StringRef S) { + std::vector 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 ArgsArr, + ArrayRef 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 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 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 ArgsArr, + ArrayRef 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 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 ArgsArr, + ArrayRef 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> 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 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 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( - 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(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> BufferOrErr = + MemoryBuffer::getFile(Src); + if (!BufferOrErr) + fatalError("Error opening file '" + Twine(Src) + + "': " + BufferOrErr.getError().message()); + std::unique_ptr &Buffer = BufferOrErr.get(); + std::unique_ptr Binary = + ExitOnErr(object::WindowsResource::createWindowsResource( + Buffer->getMemBufferRef())); + + std::vector 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 OutputBuffer = + ExitOnErr(object::writeWindowsResourceCOFF(MachineType, Parser, + /*DateTimeStamp*/ 0)); + std::unique_ptr 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 ArgsArr = makeArrayRef(Argv + 1, DashDash); + ArrayRef 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; }